Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0ae62d6f5 | ||
|
|
ea18aa7551 | ||
|
|
68e7069e92 | ||
|
|
556d929b67 | ||
|
|
7f3469a0f2 | ||
|
|
a062c4aadb | ||
|
|
3d3f81ad52 | ||
|
|
fc0fb0b3d3 | ||
|
|
884586f0af | ||
|
|
f8c18afbcf | ||
|
|
8046177d84 | ||
|
|
8d8fc91391 | ||
|
|
796fda159e | ||
|
|
4b2389dafd | ||
|
|
2dd7e5937f | ||
|
|
e9371029f3 | ||
|
|
bbec60ff02 | ||
|
|
3bf4f080c5 | ||
|
|
637c043f5b | ||
|
|
9eed993421 | ||
|
|
aff720c1f1 | ||
|
|
655de30df5 | ||
|
|
fe6217bd82 | ||
|
|
eacd7b2503 | ||
|
|
2e17fb9dd3 | ||
|
|
249543d64f | ||
|
|
8ebd490260 | ||
|
|
c0dfec8b34 | ||
|
|
56a023c98a | ||
|
|
adcac881a7 | ||
|
|
f73342d902 | ||
|
|
45b0233c21 | ||
|
|
db349519cf | ||
|
|
c760a1b1fe | ||
|
|
c5fa9039b4 | ||
|
|
7eaafc52ce | ||
|
|
63239893ab | ||
|
|
e9a1a1ced0 | ||
|
|
6dc45c9830 | ||
|
|
58f7659d55 |
No files matched your search
@@ -1,4 +1,27 @@
|
||||
# Release Notes
|
||||
## Version 0.8.6 - 06/03/2025
|
||||
### Bug Fixes
|
||||
- Fix the incorrect title positioning when adjusting the document width setting
|
||||
- Enhance the user experience of the icon color picker for smoother interactions
|
||||
- Add missing icons to the database to ensure completeness and consistency
|
||||
- Resolve the issue with links not functioning correctly on Linux systems
|
||||
- Improve the outline feature to work seamlessly within columns
|
||||
- Center the bulleted list icon within columns for better visual alignment
|
||||
- Enable dragging blocks under tables in the second column to enhance flexibility
|
||||
- Disable the AI writer feature within tables to prevent conflicts and improve usability
|
||||
- Automatically enable the header row when converting content from Markdown to ensure proper formatting
|
||||
- Use the "Undo" function to revert the auto-formatting
|
||||
|
||||
## Version 0.8.5 - 04/03/2025
|
||||
### New Features
|
||||
- Columns in Documents: Arrange content side by side using drag-and-drop or the slash menu
|
||||
- AI Writers: New AI assistants in documents with response formatting options (list, table, text with images, image-only), follow-up questions, contextual memory, and more
|
||||
- Compact Mode for Databases: Enable compact mode for grid and kanban views (full-page and inline) to increase information density, displaying more data per screen
|
||||
### Bug Fixes
|
||||
- Fixed an issue where callout blocks couldn’t be deleted when appearing as the first line in a document
|
||||
- Fixed a bug preventing the relation field in databases from opening
|
||||
- Fixed an issue where links in documents were unclickable on Linux
|
||||
|
||||
## Version 0.8.4 - 18/02/2025
|
||||
### New Features
|
||||
- Switch AI mode on mobile
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<h1 align="center" style="border-bottom: none">
|
||||
<b>
|
||||
<a href="https://www.appflowy.io">AppFlowy.IO</a><br>
|
||||
<a href="https://www.appflowy.com">AppFlowy</a><br>
|
||||
</b>
|
||||
⭐️ The Open Source Alternative To Notion ⭐️ <br>
|
||||
</h1>
|
||||
@@ -18,18 +18,18 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.appflowy.io"><b>Website</b></a> •
|
||||
<a href="https://www.appflowy.com"><b>Website</b></a> •
|
||||
<a href="https://forum.appflowy.io/"><b>Forum</b></a> •
|
||||
<a href="https://discord.gg/9Q2xaN37tV"><b>Discord</b></a> •
|
||||
<a href="https://www.reddit.com/r/AppFlowy"><b>Reddit</b></a> •
|
||||
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
|
||||
</p>
|
||||
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/tasks.796c753e.png" alt="AppFlowy Kanban Board for To-dos" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/Grid.9e30484b.png" alt="AppFlowy Databases for Tasks and Projects" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/sites.a8d5b2b9.png" alt="AppFlowy Sites for Beautiful documentation" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/ai.e1460982.png" alt="AppFlowy AI" /></p>
|
||||
<p align="center"><img src="https://appflowy.io/_next/static/media/template.9ea13c3b.png" alt="AppFlowy Templates" /></p>
|
||||
<p align="center"><img src="https://appflowy.com/_next/static/media/tasks.796c753e.png" alt="AppFlowy Kanban Board for To-dos" /></p>
|
||||
<p align="center"><img src="https://appflowy.com/_next/static/media/Grid.9e30484b.png" alt="AppFlowy Databases for Tasks and Projects" /></p>
|
||||
<p align="center"><img src="https://appflowy.com/_next/static/media/sites.a8d5b2b9.png" alt="AppFlowy Sites for Beautiful documentation" /></p>
|
||||
<p align="center"><img src="https://appflowy.com/_next/static/media/ai.e1460982.png" alt="AppFlowy AI" /></p>
|
||||
<p align="center"><img src="https://appflowy.com/_next/static/media/template.9ea13c3b.png" alt="AppFlowy Templates" /></p>
|
||||
|
||||
<br></br>
|
||||
<p align="center" >
|
||||
@@ -48,7 +48,7 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo
|
||||
- [App Store](https://apps.apple.com/app/appflowy/id6457261352): iPhone
|
||||
- [Play Store](https://play.google.com/store/apps/details?id=io.appflowy.appflowy): Android 10 or above; ARMv7 is
|
||||
not supported
|
||||
- [Self-hosting AppFlowy](https://docs.appflowy.io/docs/guides/appflowy/self-hosting-appflowy)
|
||||
- [Self-hosting AppFlowy](https://appflowy.com/docs/self-host-appflowy-overview)
|
||||
- [Source](https://docs.appflowy.io/docs/documentation/appflowy/from-source)
|
||||
|
||||
## Built With
|
||||
@@ -78,7 +78,7 @@ report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labe
|
||||
|
||||
## **Releases**
|
||||
|
||||
Please see the [changelog](https://www.appflowy.io/whatsnew) for more details about a given release.
|
||||
Please see the [changelog](https://appflowy.com/what-is-new) for more details about a given release.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -89,9 +89,7 @@ for details.
|
||||
|
||||
If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly
|
||||
easier to use or understand, **Congratulations!** If your administrative and managerial work behind the scenes sustains
|
||||
the community, **Congratulations!** You are now an official contributor to AppFlowy. Get in touch with
|
||||
us ([link](https://tally.so/r/mKP5z3)) to receive the very special Contributor T-shirt!
|
||||
Proudly wear your T-shirt and show it to us by tagging [@appflowy](https://twitter.com/appflowy) on Twitter.
|
||||
the community, **Congratulations!** You are now an official contributor to AppFlowy.
|
||||
|
||||
## Translations 🌎🗺
|
||||
|
||||
@@ -152,8 +150,8 @@ more information.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Special thanks to these amazing projects which help power AppFlowy.IO:
|
||||
Special thanks to these amazing projects which help power AppFlowy:
|
||||
|
||||
- [cargo-make](https://github.com/sagiegurari/cargo-make)
|
||||
- [contrib.rocks](https://contrib.rocks)
|
||||
- [flutter_chat_ui](https://pub.dev/packages/flutter_chat_ui)
|
||||
- [flutter_chat_ui](https://pub.dev/packages/flutter_chat_ui)
|
||||
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.8.4"
|
||||
APPFLOWY_VERSION = "0.8.6"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
+13
-10
@@ -23,24 +23,24 @@ void main() {
|
||||
final expandFinder = find.byFlowySvg(FlowySvgs.hamburger_s_s);
|
||||
|
||||
// Is expanded by default
|
||||
expect(collapseFinder, findsOneWidget);
|
||||
expect(expandFinder, findsNothing);
|
||||
|
||||
// Collapse hidden groups
|
||||
await tester.tap(collapseFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Is collapsed
|
||||
expect(collapseFinder, findsNothing);
|
||||
expect(expandFinder, findsOneWidget);
|
||||
|
||||
// Expand hidden groups
|
||||
// Collapse hidden groups
|
||||
await tester.tap(expandFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Is expanded
|
||||
// Is collapsed
|
||||
expect(collapseFinder, findsOneWidget);
|
||||
expect(expandFinder, findsNothing);
|
||||
|
||||
// Expand hidden groups
|
||||
await tester.tap(collapseFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Is expanded
|
||||
expect(collapseFinder, findsNothing);
|
||||
expect(expandFinder, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('hide first group, and show it again', (tester) async {
|
||||
@@ -48,6 +48,9 @@ void main() {
|
||||
await tester.tapAnonymousSignInButton();
|
||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
|
||||
|
||||
final expandFinder = find.byFlowySvg(FlowySvgs.hamburger_s_s);
|
||||
await tester.tapButton(expandFinder);
|
||||
|
||||
// Tap the options of the first group
|
||||
final optionsFinder = find
|
||||
.descendant(
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ void main() {
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_aiWriter.tr(),
|
||||
);
|
||||
expect(find.byType(AIWriterBlockComponent), findsOneWidget);
|
||||
expect(find.byType(AiWriterBlockComponent), findsOneWidget);
|
||||
|
||||
// switch to another page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
@@ -41,7 +41,7 @@ void main() {
|
||||
await tester.openPage(pageName);
|
||||
|
||||
// expect the ai writer block is not in the document
|
||||
expect(find.byType(AIWriterBlockComponent), findsNothing);
|
||||
expect(find.byType(AiWriterBlockComponent), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
+1
-1
@@ -57,7 +57,7 @@ void main() {
|
||||
// move the checkbox to the child of the block at path [9]
|
||||
await tester.editor.dragBlock(
|
||||
[10],
|
||||
const Offset(80, -30),
|
||||
const Offset(120, -20),
|
||||
);
|
||||
|
||||
// wait for the move animation to complete
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ void main() {
|
||||
[
|
||||
LogicalKeyboardKey.control,
|
||||
LogicalKeyboardKey.shift,
|
||||
LogicalKeyboardKey.keyE,
|
||||
LogicalKeyboardKey.keyC,
|
||||
],
|
||||
tester: tester,
|
||||
withKeyUp: true,
|
||||
|
||||
+51
@@ -4,6 +4,7 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -163,5 +164,55 @@ void main() {
|
||||
lessThan(5),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('insert inline math equation by shortcut', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: 'insert inline math equation by shortcut',
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// insert a inline page
|
||||
const formula = 'E = MC ^ 2';
|
||||
await tester.ime.insertText(formula);
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
|
||||
);
|
||||
|
||||
// mock key event
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyE,
|
||||
isShiftPressed: true,
|
||||
isControlPressed: true,
|
||||
);
|
||||
|
||||
// expect to see the math equation block
|
||||
final inlineMathEquation = find.byType(InlineMathEquation);
|
||||
expect(inlineMathEquation, findsOneWidget);
|
||||
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
const text = 'Hello World';
|
||||
await tester.ime.insertText(text);
|
||||
|
||||
final inlineText = find.textContaining(text, findRichText: true);
|
||||
expect(inlineText, findsOneWidget);
|
||||
|
||||
// the text should be in the same line with the math equation
|
||||
final inlineMathEquationPosition = tester.getRect(inlineMathEquation);
|
||||
final textPosition = tester.getRect(inlineText);
|
||||
// allow 5px difference
|
||||
expect(
|
||||
(textPosition.top - inlineMathEquationPosition.top).abs(),
|
||||
lessThan(5),
|
||||
);
|
||||
expect(
|
||||
(textPosition.bottom - inlineMathEquationPosition.bottom).abs(),
|
||||
lessThan(5),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+2
-2
@@ -48,7 +48,7 @@ void main() {
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
|
||||
offset: 80,
|
||||
offset: 100,
|
||||
);
|
||||
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
|
||||
@@ -146,7 +146,7 @@ void main() {
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_photoGallery.tr(),
|
||||
offset: 80,
|
||||
offset: 100,
|
||||
);
|
||||
expect(find.byType(MultiImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(MultiImagePlaceholder), findsOneWidget);
|
||||
|
||||
@@ -37,5 +37,20 @@ void main() {
|
||||
await tester.tap(actionWidgets.last);
|
||||
expect(find.byType(MentionPageBlock), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('create subpage with at menu', (tester) async {
|
||||
await tester.launchInAnonymousMode();
|
||||
await tester.createNewDocumentOnMobile(title);
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
const subpageName = 'Subpage';
|
||||
await tester.ime.insertText('[[$subpageName');
|
||||
await tester.pumpAndSettle();
|
||||
final actionWidgets = find.byType(MobileInlineActionsWidget);
|
||||
await tester.tapButton(actionWidgets.first);
|
||||
final firstNode =
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0]);
|
||||
assert(firstNode != null);
|
||||
expect(firstNode!.delta?.toPlainText().contains('['), false);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -617,7 +617,7 @@ extension CommonOperations on WidgetTester {
|
||||
);
|
||||
final distanceY = getCenter(to).dy - getCenter(from).dx;
|
||||
await drag(from, Offset(0, distanceY));
|
||||
await pumpAndSettle();
|
||||
await pumpAndSettle(const Duration(seconds: 1));
|
||||
}
|
||||
|
||||
// tap the button with [FlowySvgData]
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/ai/service/error.dart';
|
||||
import 'package:appflowy/ai/service/openai_client.dart';
|
||||
import 'package:appflowy/ai/service/text_completion.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MyMockClient extends Mock implements http.Client {
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
||||
final requestType = request.method;
|
||||
final requestUri = request.url;
|
||||
|
||||
if (requestType == 'POST' &&
|
||||
requestUri == OpenAIRequestType.textCompletion.uri) {
|
||||
final responseHeaders = <String, String>{
|
||||
'content-type': 'text/event-stream',
|
||||
};
|
||||
final responseBody = Stream.fromIterable([
|
||||
utf8.encode(
|
||||
'{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}',
|
||||
),
|
||||
utf8.encode('\n'),
|
||||
utf8.encode('[DONE]'),
|
||||
]);
|
||||
|
||||
// Return a mocked response with the expected data
|
||||
return http.StreamedResponse(responseBody, 200, headers: responseHeaders);
|
||||
}
|
||||
|
||||
// Return an error response for any other request
|
||||
return http.StreamedResponse(const Stream.empty(), 404);
|
||||
}
|
||||
}
|
||||
|
||||
class MockOpenAIRepository extends HttpOpenAIRepository {
|
||||
MockOpenAIRepository() : super(apiKey: 'dummyKey', client: MyMockClient());
|
||||
|
||||
@override
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
}) async {
|
||||
final request = http.Request('POST', OpenAIRequestType.textCompletion.uri);
|
||||
final response = await client.send(request);
|
||||
|
||||
String previousSyntax = '';
|
||||
if (response.statusCode == 200) {
|
||||
await for (final chunk in response.stream
|
||||
.transform(const Utf8Decoder())
|
||||
.transform(const LineSplitter())) {
|
||||
await onStart();
|
||||
final data = chunk.trim().split('data: ');
|
||||
if (data[0] != '[DONE]') {
|
||||
final response = TextCompletionResponse.fromJson(
|
||||
json.decode(data[0]),
|
||||
);
|
||||
if (response.choices.isNotEmpty) {
|
||||
final text = response.choices.first.text;
|
||||
if (text == previousSyntax && text == '\n') {
|
||||
continue;
|
||||
}
|
||||
await onProcess(response);
|
||||
previousSyntax = response.choices.first.text;
|
||||
}
|
||||
} else {
|
||||
await onEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
export 'service/ai_entities.dart';
|
||||
export 'service/ai_prompt_input_bloc.dart';
|
||||
export 'service/appflowy_ai_service.dart';
|
||||
export 'service/error.dart';
|
||||
export 'widgets/loading_indicator.dart';
|
||||
export 'widgets/prompt_input/action_buttons.dart';
|
||||
export 'widgets/prompt_input/desktop_input_text_field.dart';
|
||||
export 'widgets/prompt_input/desktop_prompt_text_field.dart';
|
||||
export 'widgets/prompt_input/file_attachment_list.dart';
|
||||
export 'widgets/prompt_input/layout_define.dart';
|
||||
export 'widgets/prompt_input/mention_page_bottom_sheet.dart';
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
import 'error.dart';
|
||||
import 'text_completion.dart';
|
||||
|
||||
abstract class AIRepository {
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
});
|
||||
|
||||
Future<void> streamCompletion({
|
||||
String? objectId,
|
||||
required String text,
|
||||
required CompletionTypePB completionType,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(String text) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
});
|
||||
|
||||
Future<FlowyResult<List<String>, AIError>> generateImage({
|
||||
required String prompt,
|
||||
int n = 1,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class PredefinedFormat extends Equatable {
|
||||
const PredefinedFormat({
|
||||
required this.imageFormat,
|
||||
required this.textFormat,
|
||||
});
|
||||
|
||||
final ImageFormat imageFormat;
|
||||
final TextFormat? textFormat;
|
||||
|
||||
PredefinedFormatPB toPB() {
|
||||
return PredefinedFormatPB(
|
||||
imageFormat: switch (imageFormat) {
|
||||
ImageFormat.text => ResponseImageFormatPB.TextOnly,
|
||||
ImageFormat.image => ResponseImageFormatPB.ImageOnly,
|
||||
ImageFormat.textAndImage => ResponseImageFormatPB.TextAndImage,
|
||||
},
|
||||
textFormat: switch (textFormat) {
|
||||
TextFormat.paragraph => ResponseTextFormatPB.Paragraph,
|
||||
TextFormat.bulletList => ResponseTextFormatPB.BulletedList,
|
||||
TextFormat.numberedList => ResponseTextFormatPB.NumberedList,
|
||||
TextFormat.table => ResponseTextFormatPB.Table,
|
||||
_ => null,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [imageFormat, textFormat];
|
||||
}
|
||||
|
||||
enum ImageFormat {
|
||||
text,
|
||||
image,
|
||||
textAndImage;
|
||||
|
||||
bool get hasText => this == text || this == textAndImage;
|
||||
|
||||
FlowySvgData get icon {
|
||||
return switch (this) {
|
||||
ImageFormat.text => FlowySvgs.ai_text_s,
|
||||
ImageFormat.image => FlowySvgs.ai_image_s,
|
||||
ImageFormat.textAndImage => FlowySvgs.ai_text_image_s,
|
||||
};
|
||||
}
|
||||
|
||||
String get i18n {
|
||||
return switch (this) {
|
||||
ImageFormat.text => LocaleKeys.chat_changeFormat_textOnly.tr(),
|
||||
ImageFormat.image => LocaleKeys.chat_changeFormat_imageOnly.tr(),
|
||||
ImageFormat.textAndImage =>
|
||||
LocaleKeys.chat_changeFormat_textAndImage.tr(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum TextFormat {
|
||||
paragraph,
|
||||
bulletList,
|
||||
numberedList,
|
||||
table;
|
||||
|
||||
FlowySvgData get icon {
|
||||
return switch (this) {
|
||||
TextFormat.paragraph => FlowySvgs.ai_paragraph_s,
|
||||
TextFormat.bulletList => FlowySvgs.ai_list_s,
|
||||
TextFormat.numberedList => FlowySvgs.ai_number_list_s,
|
||||
TextFormat.table => FlowySvgs.ai_table_s,
|
||||
};
|
||||
}
|
||||
|
||||
String get i18n {
|
||||
return switch (this) {
|
||||
TextFormat.paragraph => LocaleKeys.chat_changeFormat_text.tr(),
|
||||
TextFormat.bulletList => LocaleKeys.chat_changeFormat_bullet.tr(),
|
||||
TextFormat.numberedList => LocaleKeys.chat_changeFormat_number.tr(),
|
||||
TextFormat.table => LocaleKeys.chat_changeFormat_table.tr(),
|
||||
};
|
||||
}
|
||||
}
|
||||
+36
-4
@@ -10,12 +10,15 @@ import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'ai_entities.dart';
|
||||
|
||||
part 'ai_prompt_input_bloc.freezed.dart';
|
||||
|
||||
class AIPromptInputBloc extends Bloc<AIPromptInputEvent, AIPromptInputState> {
|
||||
AIPromptInputBloc()
|
||||
: _listener = LocalLLMListener(),
|
||||
super(AIPromptInputState.initial()) {
|
||||
AIPromptInputBloc({
|
||||
required PredefinedFormat? predefinedFormat,
|
||||
}) : _listener = LocalLLMListener(),
|
||||
super(AIPromptInputState.initial(predefinedFormat)) {
|
||||
_dispatch();
|
||||
_startListening();
|
||||
_init();
|
||||
@@ -41,6 +44,7 @@ class AIPromptInputBloc extends Bloc<AIPromptInputEvent, AIPromptInputState> {
|
||||
final aiType = chatState.pluginState.state == RunningStatePB.Running
|
||||
? AIType.localAI
|
||||
: AIType.appflowyAI;
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
aiType: aiType,
|
||||
@@ -65,6 +69,24 @@ class AIPromptInputBloc extends Bloc<AIPromptInputEvent, AIPromptInputState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
toggleShowPredefinedFormat: () {
|
||||
final predefinedFormat =
|
||||
!state.showPredefinedFormats && state.predefinedFormat == null
|
||||
? PredefinedFormat(
|
||||
imageFormat: ImageFormat.text,
|
||||
textFormat: TextFormat.paragraph,
|
||||
)
|
||||
: null;
|
||||
emit(
|
||||
state.copyWith(
|
||||
showPredefinedFormats: !state.showPredefinedFormats,
|
||||
predefinedFormat: predefinedFormat,
|
||||
),
|
||||
);
|
||||
},
|
||||
updatePredefinedFormat: (format) {
|
||||
emit(state.copyWith(predefinedFormat: format));
|
||||
},
|
||||
attachFile: (filePath, fileName) {
|
||||
final newFile = ChatFile.fromFilePath(filePath);
|
||||
if (newFile != null) {
|
||||
@@ -152,6 +174,11 @@ class AIPromptInputEvent with _$AIPromptInputEvent {
|
||||
const factory AIPromptInputEvent.updatePluginState(
|
||||
LocalAIPluginStatePB chatState,
|
||||
) = _UpdatePluginState;
|
||||
const factory AIPromptInputEvent.toggleShowPredefinedFormat() =
|
||||
_ToggleShowPredefinedFormat;
|
||||
const factory AIPromptInputEvent.updatePredefinedFormat(
|
||||
PredefinedFormat format,
|
||||
) = _UpdatePredefinedFormat;
|
||||
const factory AIPromptInputEvent.attachFile(
|
||||
String filePath,
|
||||
String fileName,
|
||||
@@ -167,14 +194,19 @@ class AIPromptInputState with _$AIPromptInputState {
|
||||
const factory AIPromptInputState({
|
||||
required AIType aiType,
|
||||
required bool supportChatWithFile,
|
||||
required bool showPredefinedFormats,
|
||||
required PredefinedFormat? predefinedFormat,
|
||||
required LocalAIChatPB? chatState,
|
||||
required List<ChatFile> attachedFiles,
|
||||
required List<ViewPB> mentionedPages,
|
||||
}) = _AIPromptInputState;
|
||||
|
||||
factory AIPromptInputState.initial() => const AIPromptInputState(
|
||||
factory AIPromptInputState.initial(PredefinedFormat? format) =>
|
||||
AIPromptInputState(
|
||||
aiType: AIType.appflowyAI,
|
||||
supportChatWithFile: false,
|
||||
showPredefinedFormats: format != null,
|
||||
predefinedFormat: format,
|
||||
chatState: null,
|
||||
attachedFiles: [],
|
||||
mentionedPages: [],
|
||||
@@ -3,137 +3,109 @@ import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/list_extension.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:fixnum/fixnum.dart' as fixnum;
|
||||
|
||||
import 'ai_client.dart';
|
||||
import 'ai_entities.dart';
|
||||
import 'error.dart';
|
||||
import 'text_completion.dart';
|
||||
|
||||
enum AskAIAction {
|
||||
summarize,
|
||||
fixSpelling,
|
||||
improveWriting,
|
||||
makeItLonger;
|
||||
|
||||
String get toInstruction => switch (this) {
|
||||
summarize => 'Tl;dr',
|
||||
fixSpelling => 'Correct this to standard English:',
|
||||
improveWriting => 'Rewrite this in your own words:',
|
||||
makeItLonger => 'Make this text longer:',
|
||||
};
|
||||
|
||||
String prompt(String input) => switch (this) {
|
||||
summarize => '$input\n\n$toInstruction',
|
||||
_ => "$toInstruction\n\n$input",
|
||||
};
|
||||
|
||||
static AskAIAction from(int index) => switch (index) {
|
||||
0 => summarize,
|
||||
1 => fixSpelling,
|
||||
2 => improveWriting,
|
||||
3 => makeItLonger,
|
||||
_ => fixSpelling
|
||||
};
|
||||
|
||||
String get name => switch (this) {
|
||||
summarize => LocaleKeys.document_plugins_smartEditSummarize.tr(),
|
||||
fixSpelling => LocaleKeys.document_plugins_smartEditFixSpelling.tr(),
|
||||
improveWriting =>
|
||||
LocaleKeys.document_plugins_smartEditImproveWriting.tr(),
|
||||
makeItLonger => LocaleKeys.document_plugins_smartEditMakeLonger.tr(),
|
||||
};
|
||||
abstract class AIRepository {
|
||||
Future<void> streamCompletion({
|
||||
String? objectId,
|
||||
required String text,
|
||||
PredefinedFormat? format,
|
||||
required CompletionTypePB completionType,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(String text) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
});
|
||||
}
|
||||
|
||||
class AppFlowyAIService implements AIRepository {
|
||||
@override
|
||||
Future<FlowyResult<List<String>, AIError>> generateImage({
|
||||
required String prompt,
|
||||
int n = 1,
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CompletionStream> streamCompletion({
|
||||
Future<(String, CompletionStream)?> streamCompletion({
|
||||
String? objectId,
|
||||
required String text,
|
||||
PredefinedFormat? format,
|
||||
List<String> sourceIds = const [],
|
||||
required CompletionTypePB completionType,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(String text) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
}) async {
|
||||
final stream = CompletionStream(
|
||||
onStart,
|
||||
onProcess,
|
||||
onEnd,
|
||||
onError,
|
||||
final stream = AppFlowyCompletionStream(
|
||||
onStart: onStart,
|
||||
onProcess: onProcess,
|
||||
onEnd: onEnd,
|
||||
onError: onError,
|
||||
);
|
||||
final List<String> ragIds = [];
|
||||
if (objectId != null) {
|
||||
ragIds.add(objectId);
|
||||
}
|
||||
|
||||
final payload = CompleteTextPB(
|
||||
text: text,
|
||||
completionType: completionType,
|
||||
format: format?.toPB(),
|
||||
streamPort: fixnum.Int64(stream.nativePort),
|
||||
objectId: objectId ?? "",
|
||||
ragIds: ragIds,
|
||||
objectId: objectId ?? '',
|
||||
ragIds: [
|
||||
if (objectId != null) objectId,
|
||||
...sourceIds,
|
||||
].unique(),
|
||||
);
|
||||
|
||||
// ignore: unawaited_futures
|
||||
AIEventCompleteText(payload).send();
|
||||
return stream;
|
||||
return AIEventCompleteText(payload).send().fold(
|
||||
(task) => (task.taskId, stream),
|
||||
(error) {
|
||||
Log.error(error);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CompletionTypePB completionTypeFromInt(AskAIAction action) {
|
||||
switch (action) {
|
||||
case AskAIAction.summarize:
|
||||
return CompletionTypePB.MakeShorter;
|
||||
case AskAIAction.fixSpelling:
|
||||
return CompletionTypePB.SpellingAndGrammar;
|
||||
case AskAIAction.improveWriting:
|
||||
return CompletionTypePB.ImproveWriting;
|
||||
case AskAIAction.makeItLonger:
|
||||
return CompletionTypePB.MakeLonger;
|
||||
}
|
||||
abstract class CompletionStream {
|
||||
CompletionStream({
|
||||
required this.onStart,
|
||||
required this.onProcess,
|
||||
required this.onEnd,
|
||||
required this.onError,
|
||||
});
|
||||
|
||||
final Future<void> Function() onStart;
|
||||
final Future<void> Function(String text) onProcess;
|
||||
final Future<void> Function() onEnd;
|
||||
final void Function(AIError error) onError;
|
||||
}
|
||||
|
||||
class CompletionStream {
|
||||
CompletionStream(
|
||||
Future<void> Function() onStart,
|
||||
Future<void> Function(String text) onProcess,
|
||||
Future<void> Function() onEnd,
|
||||
void Function(AIError error) onError,
|
||||
) {
|
||||
class AppFlowyCompletionStream extends CompletionStream {
|
||||
AppFlowyCompletionStream({
|
||||
required super.onStart,
|
||||
required super.onProcess,
|
||||
required super.onEnd,
|
||||
required super.onError,
|
||||
}) {
|
||||
_startListening();
|
||||
}
|
||||
|
||||
final RawReceivePort _port = RawReceivePort();
|
||||
final StreamController<String> _controller = StreamController.broadcast();
|
||||
late StreamSubscription<String> _subscription;
|
||||
int get nativePort => _port.sendPort.nativePort;
|
||||
|
||||
void _startListening() {
|
||||
_port.handler = _controller.add;
|
||||
_subscription = _controller.stream.listen(
|
||||
(event) async {
|
||||
if (event == "AI_RESPONSE_LIMIT") {
|
||||
onError(
|
||||
AIError(
|
||||
message: LocaleKeys.sideBar_aiResponseLimit.tr(),
|
||||
message: LocaleKeys.ai_textLimitReachedDescription.tr(),
|
||||
code: AIErrorCode.aiResponseLimitExceeded,
|
||||
),
|
||||
);
|
||||
@@ -142,7 +114,7 @@ class CompletionStream {
|
||||
if (event == "AI_IMAGE_RESPONSE_LIMIT") {
|
||||
onError(
|
||||
AIError(
|
||||
message: LocaleKeys.sideBar_aiImageResponseLimit.tr(),
|
||||
message: LocaleKeys.ai_imageLimitReachedDescription.tr(),
|
||||
code: AIErrorCode.aiImageResponseLimitExceeded,
|
||||
),
|
||||
);
|
||||
@@ -153,6 +125,7 @@ class CompletionStream {
|
||||
onError(
|
||||
AIError(
|
||||
message: msg,
|
||||
code: AIErrorCode.other,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -170,26 +143,17 @@ class CompletionStream {
|
||||
}
|
||||
|
||||
if (event.startsWith("error:")) {
|
||||
onError(AIError(message: event.substring(6)));
|
||||
onError(
|
||||
AIError(message: event.substring(6), code: AIErrorCode.other),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final RawReceivePort _port = RawReceivePort();
|
||||
final StreamController<String> _controller = StreamController.broadcast();
|
||||
late StreamSubscription<String> _subscription;
|
||||
int get nativePort => _port.sendPort.nativePort;
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _controller.close();
|
||||
await _subscription.cancel();
|
||||
_port.close();
|
||||
}
|
||||
|
||||
StreamSubscription<String> listen(
|
||||
void Function(String event)? onData,
|
||||
) {
|
||||
return _controller.stream.listen(onData);
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ part 'error.g.dart';
|
||||
class AIError with _$AIError {
|
||||
const factory AIError({
|
||||
required String message,
|
||||
@Default(AIErrorCode.other) AIErrorCode code,
|
||||
required AIErrorCode code,
|
||||
}) = _AIError;
|
||||
|
||||
factory AIError.fromJson(Map<String, Object?> json) =>
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbenum.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'ai_client.dart';
|
||||
import 'error.dart';
|
||||
import 'text_completion.dart';
|
||||
|
||||
enum OpenAIRequestType {
|
||||
textCompletion,
|
||||
textEdit,
|
||||
imageGenerations;
|
||||
|
||||
Uri get uri {
|
||||
switch (this) {
|
||||
case OpenAIRequestType.textCompletion:
|
||||
return Uri.parse('https://api.openai.com/v1/completions');
|
||||
case OpenAIRequestType.textEdit:
|
||||
return Uri.parse('https://api.openai.com/v1/chat/completions');
|
||||
case OpenAIRequestType.imageGenerations:
|
||||
return Uri.parse('https://api.openai.com/v1/images/generations');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HttpOpenAIRepository implements AIRepository {
|
||||
const HttpOpenAIRepository({
|
||||
required this.client,
|
||||
required this.apiKey,
|
||||
});
|
||||
|
||||
final http.Client client;
|
||||
final String apiKey;
|
||||
|
||||
Map<String, String> get headers => {
|
||||
'Authorization': 'Bearer $apiKey',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
@override
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
}) async {
|
||||
final parameters = {
|
||||
'model': 'gpt-3.5-turbo-instruct',
|
||||
'prompt': prompt,
|
||||
'suffix': suffix,
|
||||
'max_tokens': maxTokens,
|
||||
'temperature': temperature,
|
||||
'stream': true,
|
||||
};
|
||||
|
||||
final request = http.Request('POST', OpenAIRequestType.textCompletion.uri);
|
||||
request.headers.addAll(headers);
|
||||
request.body = jsonEncode(parameters);
|
||||
|
||||
final response = await client.send(request);
|
||||
|
||||
// NEED TO REFACTOR.
|
||||
// WHY OPENAI USE TWO LINES TO INDICATE THE START OF THE STREAMING RESPONSE?
|
||||
// AND WHY OPENAI USE [DONE] TO INDICATE THE END OF THE STREAMING RESPONSE?
|
||||
int syntax = 0;
|
||||
var previousSyntax = '';
|
||||
if (response.statusCode == 200) {
|
||||
await for (final chunk in response.stream
|
||||
.transform(const Utf8Decoder())
|
||||
.transform(const LineSplitter())) {
|
||||
syntax += 1;
|
||||
if (!useAction) {
|
||||
if (syntax == 3) {
|
||||
await onStart();
|
||||
continue;
|
||||
} else if (syntax < 3) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (syntax == 2) {
|
||||
await onStart();
|
||||
continue;
|
||||
} else if (syntax < 2) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final data = chunk.trim().split('data: ');
|
||||
if (data.length > 1) {
|
||||
if (data[1] != '[DONE]') {
|
||||
final response = TextCompletionResponse.fromJson(
|
||||
json.decode(data[1]),
|
||||
);
|
||||
if (response.choices.isNotEmpty) {
|
||||
final text = response.choices.first.text;
|
||||
if (text == previousSyntax && text == '\n') {
|
||||
continue;
|
||||
}
|
||||
await onProcess(response);
|
||||
previousSyntax = response.choices.first.text;
|
||||
}
|
||||
} else {
|
||||
await onEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final body = await response.stream.bytesToString();
|
||||
onError(
|
||||
AIError.fromJson(json.decode(body)['error']),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<List<String>, AIError>> generateImage({
|
||||
required String prompt,
|
||||
int n = 1,
|
||||
}) async {
|
||||
final parameters = {
|
||||
'prompt': prompt,
|
||||
'n': n,
|
||||
'size': '512x512',
|
||||
};
|
||||
|
||||
try {
|
||||
final response = await client.post(
|
||||
OpenAIRequestType.imageGenerations.uri,
|
||||
headers: headers,
|
||||
body: json.encode(parameters),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(
|
||||
utf8.decode(response.bodyBytes),
|
||||
)['data'] as List;
|
||||
final urls = data
|
||||
.map((e) => e.values)
|
||||
.expand((e) => e)
|
||||
.map((e) => e.toString())
|
||||
.toList();
|
||||
return FlowyResult.success(urls);
|
||||
} else {
|
||||
return FlowyResult.failure(
|
||||
AIError.fromJson(json.decode(response.body)['error']),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return FlowyResult.failure(AIError(message: error.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> streamCompletion({
|
||||
String? objectId,
|
||||
required String text,
|
||||
required CompletionTypePB completionType,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(String text) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'text_completion.freezed.dart';
|
||||
part 'text_completion.g.dart';
|
||||
|
||||
@freezed
|
||||
class TextCompletionChoice with _$TextCompletionChoice {
|
||||
factory TextCompletionChoice({
|
||||
required String text,
|
||||
required int index,
|
||||
// ignore: invalid_annotation_target
|
||||
@JsonKey(name: 'finish_reason') String? finishReason,
|
||||
}) = _TextCompletionChoice;
|
||||
|
||||
factory TextCompletionChoice.fromJson(Map<String, Object?> json) =>
|
||||
_$TextCompletionChoiceFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class TextCompletionResponse with _$TextCompletionResponse {
|
||||
const factory TextCompletionResponse({
|
||||
required List<TextCompletionChoice> choices,
|
||||
}) = _TextCompletionResponse;
|
||||
|
||||
factory TextCompletionResponse.fromJson(Map<String, Object?> json) =>
|
||||
_$TextCompletionResponseFromJson(json);
|
||||
}
|
||||
@@ -16,51 +16,48 @@ class AILoadingIndicator extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final slice = Duration(milliseconds: duration.inMilliseconds ~/ 5);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: SizedBox(
|
||||
height: 20,
|
||||
child: SeparatedRow(
|
||||
separatorBuilder: () => const HSpace(4),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 4.0),
|
||||
child: FlowyText(
|
||||
text,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: SeparatedRow(
|
||||
separatorBuilder: () => const HSpace(4),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 4.0),
|
||||
child: FlowyText(
|
||||
text,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
buildDot(const Color(0xFF9327FF))
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.slideY(duration: slice, begin: 0, end: -1)
|
||||
.then()
|
||||
.slideY(begin: -1, end: 1)
|
||||
.then()
|
||||
.slideY(begin: 1, end: 0)
|
||||
.then()
|
||||
.slideY(duration: slice * 2, begin: 0, end: 0),
|
||||
buildDot(const Color(0xFFFB006D))
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.slideY(duration: slice, begin: 0, end: 0)
|
||||
.then()
|
||||
.slideY(begin: 0, end: -1)
|
||||
.then()
|
||||
.slideY(begin: -1, end: 1)
|
||||
.then()
|
||||
.slideY(begin: 1, end: 0)
|
||||
.then()
|
||||
.slideY(begin: 0, end: 0),
|
||||
buildDot(const Color(0xFFFFCE00))
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.slideY(duration: slice * 2, begin: 0, end: 0)
|
||||
.then()
|
||||
.slideY(duration: slice, begin: 0, end: -1)
|
||||
.then()
|
||||
.slideY(begin: -1, end: 1)
|
||||
.then()
|
||||
.slideY(begin: 1, end: 0),
|
||||
],
|
||||
),
|
||||
),
|
||||
buildDot(const Color(0xFF9327FF))
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.slideY(duration: slice, begin: 0, end: -1)
|
||||
.then()
|
||||
.slideY(begin: -1, end: 1)
|
||||
.then()
|
||||
.slideY(begin: 1, end: 0)
|
||||
.then()
|
||||
.slideY(duration: slice * 2, begin: 0, end: 0),
|
||||
buildDot(const Color(0xFFFB006D))
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.slideY(duration: slice, begin: 0, end: 0)
|
||||
.then()
|
||||
.slideY(begin: 0, end: -1)
|
||||
.then()
|
||||
.slideY(begin: -1, end: 1)
|
||||
.then()
|
||||
.slideY(begin: 1, end: 0)
|
||||
.then()
|
||||
.slideY(begin: 0, end: 0),
|
||||
buildDot(const Color(0xFFFFCE00))
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.slideY(duration: slice * 2, begin: 0, end: 0)
|
||||
.then()
|
||||
.slideY(duration: slice, begin: 0, end: -1)
|
||||
.then()
|
||||
.slideY(begin: -1, end: 1)
|
||||
.then()
|
||||
.slideY(begin: 1, end: 0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/layout_define.dart';
|
||||
import 'package:extended_text_field/extended_text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'mentioned_page_text_span.dart';
|
||||
|
||||
class PromptInputTextField extends StatelessWidget {
|
||||
const PromptInputTextField({
|
||||
super.key,
|
||||
required this.cubit,
|
||||
required this.textController,
|
||||
required this.textFieldFocusNode,
|
||||
required this.contentPadding,
|
||||
this.hintText = "",
|
||||
});
|
||||
|
||||
final ChatInputControlCubit cubit;
|
||||
final TextEditingController textController;
|
||||
final FocusNode textFieldFocusNode;
|
||||
final EdgeInsetsGeometry contentPadding;
|
||||
final String hintText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExtendedTextField(
|
||||
controller: textController,
|
||||
focusNode: textFieldFocusNode,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
contentPadding: contentPadding,
|
||||
hintText: hintText,
|
||||
hintStyle: AIChatUILayout.inputHintTextStyle(context),
|
||||
isCollapsed: true,
|
||||
isDense: true,
|
||||
),
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
minLines: 1,
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
specialTextSpanBuilder: PromptInputTextSpanBuilder(
|
||||
inputControlCubit: cubit,
|
||||
specialTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+190
-115
@@ -1,41 +1,42 @@
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/ai_prompt_input_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/layout_define.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:extended_text_field/extended_text_field.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../layout_define.dart';
|
||||
|
||||
class DesktopChatInput extends StatefulWidget {
|
||||
const DesktopChatInput({
|
||||
class DesktopPromptInput extends StatefulWidget {
|
||||
const DesktopPromptInput({
|
||||
super.key,
|
||||
required this.chatId,
|
||||
required this.isStreaming,
|
||||
required this.onStopStreaming,
|
||||
required this.onSubmitted,
|
||||
required this.selectedSourcesNotifier,
|
||||
required this.onUpdateSelectedSources,
|
||||
this.hideDecoration = false,
|
||||
});
|
||||
|
||||
final String chatId;
|
||||
final bool isStreaming;
|
||||
final void Function() onStopStreaming;
|
||||
final void Function(String, PredefinedFormat?, Map<String, dynamic>)
|
||||
onSubmitted;
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final void Function(List<String>) onUpdateSelectedSources;
|
||||
final bool hideDecoration;
|
||||
|
||||
@override
|
||||
State<DesktopChatInput> createState() => _DesktopChatInputState();
|
||||
State<DesktopPromptInput> createState() => _DesktopPromptInputState();
|
||||
}
|
||||
|
||||
class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
class _DesktopPromptInputState extends State<DesktopPromptInput> {
|
||||
final textFieldKey = GlobalKey();
|
||||
final layerLink = LayerLink();
|
||||
final overlayController = OverlayPortalController();
|
||||
@@ -43,11 +44,6 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
final focusNode = FocusNode();
|
||||
final textController = TextEditingController();
|
||||
|
||||
bool showPredefinedFormatSection = true;
|
||||
PredefinedFormat predefinedFormat = const PredefinedFormat(
|
||||
imageFormat: ImageFormat.text,
|
||||
textFormat: TextFormat.bulletList,
|
||||
);
|
||||
late SendButtonState sendButtonState;
|
||||
bool isComposing = false;
|
||||
|
||||
@@ -57,13 +53,15 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
|
||||
textController.addListener(handleTextControllerChanged);
|
||||
|
||||
// refresh border color on focus change and hide menu when lost focus
|
||||
focusNode.addListener(
|
||||
() => setState(() {
|
||||
if (!focusNode.hasFocus) {
|
||||
cancelMentionPage();
|
||||
() {
|
||||
if (!widget.hideDecoration) {
|
||||
setState(() {}); // refresh border color
|
||||
}
|
||||
}),
|
||||
if (!focusNode.hasFocus) {
|
||||
cancelMentionPage(); // hide menu when lost focus
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
updateSendButtonState();
|
||||
@@ -112,15 +110,7 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
);
|
||||
},
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: focusNode.hasFocus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
width: focusNode.hasFocus ? 1.5 : 1.0,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
),
|
||||
decoration: decoration(context),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -132,7 +122,6 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
),
|
||||
child: TextFieldTapRegion(
|
||||
child: PromptInputFile(
|
||||
chatId: widget.chatId,
|
||||
onDeleted: (file) => context
|
||||
.read<AIPromptInputBloc>()
|
||||
.add(AIPromptInputEvent.removeFile(file)),
|
||||
@@ -140,53 +129,63 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
),
|
||||
),
|
||||
const VSpace(4.0),
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
constraints: getTextFieldConstraints(),
|
||||
child: inputTextField(),
|
||||
),
|
||||
if (showPredefinedFormatSection)
|
||||
Positioned.fill(
|
||||
bottom: null,
|
||||
child: TextFieldTapRegion(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsetsDirectional.only(start: 8.0),
|
||||
child: ChangeFormatBar(
|
||||
predefinedFormat: predefinedFormat,
|
||||
spacing: 4.0,
|
||||
onSelectPredefinedFormat: (format) {
|
||||
setState(() => predefinedFormat = format);
|
||||
},
|
||||
BlocBuilder<AIPromptInputBloc, AIPromptInputState>(
|
||||
builder: (context, state) {
|
||||
return Stack(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: getTextFieldConstraints(
|
||||
state.showPredefinedFormats,
|
||||
),
|
||||
child: inputTextField(),
|
||||
),
|
||||
if (state.showPredefinedFormats)
|
||||
Positioned.fill(
|
||||
bottom: null,
|
||||
child: TextFieldTapRegion(
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(
|
||||
start: 8.0,
|
||||
),
|
||||
child: ChangeFormatBar(
|
||||
predefinedFormat: state.predefinedFormat,
|
||||
spacing: 4.0,
|
||||
onSelectPredefinedFormat: (format) =>
|
||||
context.read<AIPromptInputBloc>().add(
|
||||
AIPromptInputEvent
|
||||
.updatePredefinedFormat(
|
||||
format,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
top: null,
|
||||
child: TextFieldTapRegion(
|
||||
child: _PromptBottomActions(
|
||||
showPredefinedFormats:
|
||||
state.showPredefinedFormats,
|
||||
onTogglePredefinedFormatSection: () =>
|
||||
context.read<AIPromptInputBloc>().add(
|
||||
AIPromptInputEvent
|
||||
.toggleShowPredefinedFormat(),
|
||||
),
|
||||
onStartMention: startMentionPageFromButton,
|
||||
sendButtonState: sendButtonState,
|
||||
onSendPressed: handleSend,
|
||||
onStopStreaming: widget.onStopStreaming,
|
||||
selectedSourcesNotifier:
|
||||
widget.selectedSourcesNotifier,
|
||||
onUpdateSelectedSources:
|
||||
widget.onUpdateSelectedSources,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
top: null,
|
||||
child: TextFieldTapRegion(
|
||||
child: _PromptBottomActions(
|
||||
textController: textController,
|
||||
overlayController: overlayController,
|
||||
focusNode: focusNode,
|
||||
showPredefinedFormats: showPredefinedFormatSection,
|
||||
predefinedFormat: predefinedFormat,
|
||||
onTogglePredefinedFormatSection: () {
|
||||
setState(() {
|
||||
showPredefinedFormatSection =
|
||||
!showPredefinedFormatSection;
|
||||
});
|
||||
},
|
||||
sendButtonState: sendButtonState,
|
||||
onSendPressed: handleSend,
|
||||
onStopStreaming: widget.onStopStreaming,
|
||||
onUpdateSelectedSources:
|
||||
widget.onUpdateSelectedSources,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -196,6 +195,40 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
);
|
||||
}
|
||||
|
||||
BoxDecoration decoration(BuildContext context) {
|
||||
if (widget.hideDecoration) {
|
||||
return BoxDecoration();
|
||||
}
|
||||
return BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border.all(
|
||||
color: focusNode.hasFocus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
width: focusNode.hasFocus ? 1.5 : 1.0,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
);
|
||||
}
|
||||
|
||||
void startMentionPageFromButton() {
|
||||
if (overlayController.isShowing) {
|
||||
return;
|
||||
}
|
||||
if (!focusNode.hasFocus) {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
textController.text += '@';
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
context
|
||||
.read<ChatInputControlCubit>()
|
||||
.startSearching(textController.value);
|
||||
overlayController.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void cancelMentionPage() {
|
||||
if (overlayController.isShowing) {
|
||||
inputControlCubit.reset();
|
||||
@@ -228,9 +261,13 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
// get the attached files and mentioned pages
|
||||
final metadata = context.read<AIPromptInputBloc>().consumeMetadata();
|
||||
|
||||
final bloc = context.read<AIPromptInputBloc>();
|
||||
final showPredefinedFormats = bloc.state.showPredefinedFormats;
|
||||
final predefinedFormat = bloc.state.predefinedFormat;
|
||||
|
||||
widget.onSubmitted(
|
||||
trimmedText,
|
||||
showPredefinedFormatSection ? predefinedFormat : null,
|
||||
showPredefinedFormats ? predefinedFormat : null,
|
||||
metadata,
|
||||
);
|
||||
}
|
||||
@@ -330,18 +367,6 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
overlayController.hide();
|
||||
}
|
||||
|
||||
BoxConstraints getTextFieldConstraints() {
|
||||
double minHeight = DesktopAIPromptSizes.textFieldMinHeight +
|
||||
DesktopAIPromptSizes.actionBarSendButtonSize +
|
||||
DesktopAIChatSizes.inputActionBarMargin.vertical;
|
||||
double maxHeight = 300;
|
||||
if (showPredefinedFormatSection) {
|
||||
minHeight += DesktopAIPromptSizes.predefinedFormatButtonHeight;
|
||||
maxHeight += DesktopAIPromptSizes.predefinedFormatButtonHeight;
|
||||
}
|
||||
return BoxConstraints(minHeight: minHeight, maxHeight: maxHeight);
|
||||
}
|
||||
|
||||
Widget inputTextField() {
|
||||
return Shortcuts(
|
||||
shortcuts: buildShortcuts(),
|
||||
@@ -356,7 +381,8 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
cubit: inputControlCubit,
|
||||
textController: textController,
|
||||
textFieldFocusNode: focusNode,
|
||||
contentPadding: calculateContentPadding(),
|
||||
contentPadding:
|
||||
calculateContentPadding(state.showPredefinedFormats),
|
||||
hintText: switch (state.aiType) {
|
||||
AIType.appflowyAI => LocaleKeys.chat_inputMessageHint.tr(),
|
||||
AIType.localAI => LocaleKeys.chat_inputLocalAIMessageHint.tr()
|
||||
@@ -369,8 +395,20 @@ class _DesktopChatInputState extends State<DesktopChatInput> {
|
||||
);
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry calculateContentPadding() {
|
||||
final top = showPredefinedFormatSection
|
||||
BoxConstraints getTextFieldConstraints(bool showPredefinedFormats) {
|
||||
double minHeight = DesktopAIPromptSizes.textFieldMinHeight +
|
||||
DesktopAIPromptSizes.actionBarSendButtonSize +
|
||||
DesktopAIChatSizes.inputActionBarMargin.vertical;
|
||||
double maxHeight = 300;
|
||||
if (showPredefinedFormats) {
|
||||
minHeight += DesktopAIPromptSizes.predefinedFormatButtonHeight;
|
||||
maxHeight += DesktopAIPromptSizes.predefinedFormatButtonHeight;
|
||||
}
|
||||
return BoxConstraints(minHeight: minHeight, maxHeight: maxHeight);
|
||||
}
|
||||
|
||||
EdgeInsetsGeometry calculateContentPadding(bool showPredefinedFormats) {
|
||||
final top = showPredefinedFormats
|
||||
? DesktopAIPromptSizes.predefinedFormatButtonHeight
|
||||
: 0.0;
|
||||
final bottom = DesktopAIPromptSizes.actionBarSendButtonSize +
|
||||
@@ -451,29 +489,80 @@ class _FocusNextItemIntent extends Intent {
|
||||
const _FocusNextItemIntent();
|
||||
}
|
||||
|
||||
class PromptInputTextField extends StatelessWidget {
|
||||
const PromptInputTextField({
|
||||
super.key,
|
||||
required this.cubit,
|
||||
required this.textController,
|
||||
required this.textFieldFocusNode,
|
||||
required this.contentPadding,
|
||||
this.hintText = "",
|
||||
});
|
||||
|
||||
final ChatInputControlCubit cubit;
|
||||
final TextEditingController textController;
|
||||
final FocusNode textFieldFocusNode;
|
||||
final EdgeInsetsGeometry contentPadding;
|
||||
final String hintText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExtendedTextField(
|
||||
controller: textController,
|
||||
focusNode: textFieldFocusNode,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
contentPadding: contentPadding,
|
||||
hintText: hintText,
|
||||
hintStyle: inputHintTextStyle(context),
|
||||
isCollapsed: true,
|
||||
isDense: true,
|
||||
),
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
minLines: 1,
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
specialTextSpanBuilder: PromptInputTextSpanBuilder(
|
||||
inputControlCubit: cubit,
|
||||
specialTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? inputHintTextStyle(BuildContext context) {
|
||||
return Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).isLightMode
|
||||
? const Color(0xFFBDC2C8)
|
||||
: const Color(0xFF3C3E51),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PromptBottomActions extends StatelessWidget {
|
||||
const _PromptBottomActions({
|
||||
required this.textController,
|
||||
required this.overlayController,
|
||||
required this.focusNode,
|
||||
required this.sendButtonState,
|
||||
required this.predefinedFormat,
|
||||
required this.onTogglePredefinedFormatSection,
|
||||
required this.showPredefinedFormats,
|
||||
required this.onTogglePredefinedFormatSection,
|
||||
required this.onStartMention,
|
||||
required this.onSendPressed,
|
||||
required this.onStopStreaming,
|
||||
required this.selectedSourcesNotifier,
|
||||
required this.onUpdateSelectedSources,
|
||||
});
|
||||
|
||||
final TextEditingController textController;
|
||||
final OverlayPortalController overlayController;
|
||||
final FocusNode focusNode;
|
||||
final bool showPredefinedFormats;
|
||||
final PredefinedFormat predefinedFormat;
|
||||
final void Function() onTogglePredefinedFormatSection;
|
||||
final void Function() onStartMention;
|
||||
final SendButtonState sendButtonState;
|
||||
final void Function() onSendPressed;
|
||||
final void Function() onStopStreaming;
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final void Function(List<String>) onUpdateSelectedSources;
|
||||
|
||||
@override
|
||||
@@ -520,7 +609,6 @@ class _PromptBottomActions extends StatelessWidget {
|
||||
Widget _predefinedFormatButton() {
|
||||
return PromptInputDesktopToggleFormatButton(
|
||||
showFormatBar: showPredefinedFormats,
|
||||
predefinedFormat: predefinedFormat,
|
||||
onTap: onTogglePredefinedFormatSection,
|
||||
);
|
||||
}
|
||||
@@ -528,6 +616,7 @@ class _PromptBottomActions extends StatelessWidget {
|
||||
Widget _selectSourcesButton(BuildContext context) {
|
||||
return PromptInputDesktopSelectSourcesButton(
|
||||
onUpdateSelectedSources: onUpdateSelectedSources,
|
||||
selectedSourcesNotifier: selectedSourcesNotifier,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -535,21 +624,7 @@ class _PromptBottomActions extends StatelessWidget {
|
||||
// return PromptInputMentionButton(
|
||||
// iconSize: DesktopAIPromptSizes.actionBarIconSize,
|
||||
// buttonSize: DesktopAIPromptSizes.actionBarButtonSize,
|
||||
// onTap: () {
|
||||
// if (overlayController.isShowing) {
|
||||
// return;
|
||||
// }
|
||||
// if (!focusNode.hasFocus) {
|
||||
// focusNode.requestFocus();
|
||||
// }
|
||||
// textController.text += '@';
|
||||
// Future.delayed(Duration.zero, () {
|
||||
// context
|
||||
// .read<ChatInputControlCubit>()
|
||||
// .startSearching(textController.value);
|
||||
// overlayController.show();
|
||||
// });
|
||||
// },
|
||||
// onTap: onStartMention,
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/ai_prompt_input_bloc.dart';
|
||||
import 'package:appflowy/ai/service/ai_prompt_input_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_file_bloc.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
@@ -13,11 +13,9 @@ import 'layout_define.dart';
|
||||
class PromptInputFile extends StatelessWidget {
|
||||
const PromptInputFile({
|
||||
super.key,
|
||||
required this.chatId,
|
||||
required this.onDeleted,
|
||||
});
|
||||
|
||||
final String chatId;
|
||||
final void Function(ChatFile) onDeleted;
|
||||
|
||||
@override
|
||||
@@ -37,7 +35,6 @@ class PromptInputFile extends StatelessWidget {
|
||||
),
|
||||
itemCount: files.length,
|
||||
itemBuilder: (context, index) => ChatFilePreview(
|
||||
chatId: chatId,
|
||||
file: files[index],
|
||||
onDeleted: () => onDeleted(files[index]),
|
||||
),
|
||||
@@ -49,13 +46,11 @@ class PromptInputFile extends StatelessWidget {
|
||||
|
||||
class ChatFilePreview extends StatefulWidget {
|
||||
const ChatFilePreview({
|
||||
required this.chatId,
|
||||
required this.file,
|
||||
required this.onDeleted,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String chatId;
|
||||
final ChatFile file;
|
||||
final VoidCallback onDeleted;
|
||||
|
||||
|
||||
+1
-3
@@ -1,24 +1,22 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../service/ai_entities.dart';
|
||||
import 'layout_define.dart';
|
||||
|
||||
class PromptInputDesktopToggleFormatButton extends StatelessWidget {
|
||||
const PromptInputDesktopToggleFormatButton({
|
||||
super.key,
|
||||
required this.showFormatBar,
|
||||
required this.predefinedFormat,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final bool showFormatBar;
|
||||
final PredefinedFormat predefinedFormat;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
|
||||
+53
-53
@@ -2,7 +2,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart';
|
||||
import 'package:appflowy/plugins/base/drag_handler.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
@@ -19,9 +18,11 @@ import 'select_sources_menu.dart';
|
||||
class PromptInputMobileSelectSourcesButton extends StatefulWidget {
|
||||
const PromptInputMobileSelectSourcesButton({
|
||||
super.key,
|
||||
required this.selectedSourcesNotifier,
|
||||
required this.onUpdateSelectedSources,
|
||||
});
|
||||
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final void Function(List<String>) onUpdateSelectedSources;
|
||||
|
||||
@override
|
||||
@@ -36,15 +37,15 @@ class _PromptInputMobileSelectSourcesButtonState
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.selectedSourcesNotifier.addListener(onSelectedSourcesChanged);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
cubit.updateSelectedSources(
|
||||
context.read<ChatBloc>().state.selectedSourceIds,
|
||||
);
|
||||
onSelectedSourcesChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.selectedSourcesNotifier.removeListener(onSelectedSourcesChanged);
|
||||
cubit.close();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -70,56 +71,49 @@ class _PromptInputMobileSelectSourcesButtonState
|
||||
],
|
||||
child: BlocBuilder<SpaceBloc, SpaceState>(
|
||||
builder: (context, state) {
|
||||
return BlocListener<ChatBloc, ChatState>(
|
||||
listener: (context, state) {
|
||||
cubit
|
||||
..updateSelectedSources(state.selectedSourceIds)
|
||||
..updateSelectedStatus();
|
||||
},
|
||||
child: FlowyButton(
|
||||
margin: const EdgeInsetsDirectional.fromSTEB(4, 6, 2, 6),
|
||||
expandText: false,
|
||||
text: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowySvg(
|
||||
FlowySvgs.ai_page_s,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: const Size.square(20.0),
|
||||
),
|
||||
FlowySvg(
|
||||
FlowySvgs.ai_source_drop_down_s,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: const Size.square(10),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
context
|
||||
.read<ChatSettingsCubit>()
|
||||
.refreshSources(state.spaces, state.currentSpace);
|
||||
await showMobileBottomSheet<void>(
|
||||
context,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
maxChildSize: 0.98,
|
||||
enableDraggableScrollable: true,
|
||||
scrollableWidgetBuilder: (_, scrollController) {
|
||||
return Expanded(
|
||||
child: BlocProvider.value(
|
||||
value: cubit,
|
||||
child: _MobileSelectSourcesSheetBody(
|
||||
scrollController: scrollController,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context) => const SizedBox.shrink(),
|
||||
);
|
||||
if (context.mounted) {
|
||||
widget.onUpdateSelectedSources(cubit.selectedSourceIds);
|
||||
}
|
||||
},
|
||||
return FlowyButton(
|
||||
margin: const EdgeInsetsDirectional.fromSTEB(4, 6, 2, 6),
|
||||
expandText: false,
|
||||
text: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowySvg(
|
||||
FlowySvgs.ai_page_s,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: const Size.square(20.0),
|
||||
),
|
||||
FlowySvg(
|
||||
FlowySvgs.ai_source_drop_down_s,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: const Size.square(10),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
context
|
||||
.read<ChatSettingsCubit>()
|
||||
.refreshSources(state.spaces, state.currentSpace);
|
||||
await showMobileBottomSheet<void>(
|
||||
context,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
maxChildSize: 0.98,
|
||||
enableDraggableScrollable: true,
|
||||
scrollableWidgetBuilder: (_, scrollController) {
|
||||
return Expanded(
|
||||
child: BlocProvider.value(
|
||||
value: cubit,
|
||||
child: _MobileSelectSourcesSheetBody(
|
||||
scrollController: scrollController,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context) => const SizedBox.shrink(),
|
||||
);
|
||||
if (context.mounted) {
|
||||
widget.onUpdateSelectedSources(cubit.selectedSourceIds);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -127,6 +121,12 @@ class _PromptInputMobileSelectSourcesButtonState
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void onSelectedSourcesChanged() {
|
||||
cubit
|
||||
..updateSelectedSources(widget.selectedSourcesNotifier.value)
|
||||
..updateSelectedStatus();
|
||||
}
|
||||
}
|
||||
|
||||
class _MobileSelectSourcesSheetBody extends StatefulWidget {
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'dart:math';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
@@ -24,9 +24,11 @@ import 'mention_page_menu.dart';
|
||||
class PromptInputDesktopSelectSourcesButton extends StatefulWidget {
|
||||
const PromptInputDesktopSelectSourcesButton({
|
||||
super.key,
|
||||
required this.selectedSourcesNotifier,
|
||||
required this.onUpdateSelectedSources,
|
||||
});
|
||||
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final void Function(List<String>) onUpdateSelectedSources;
|
||||
|
||||
@override
|
||||
@@ -42,15 +44,15 @@ class _PromptInputDesktopSelectSourcesButtonState
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.selectedSourcesNotifier.addListener(onSelectedSourcesChanged);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
cubit.updateSelectedSources(
|
||||
context.read<ChatBloc>().state.selectedSourceIds,
|
||||
);
|
||||
onSelectedSourcesChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.selectedSourcesNotifier.removeListener(onSelectedSourcesChanged);
|
||||
cubit.close();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -76,51 +78,53 @@ class _PromptInputDesktopSelectSourcesButtonState
|
||||
],
|
||||
child: BlocBuilder<SpaceBloc, SpaceState>(
|
||||
builder: (context, state) {
|
||||
return BlocListener<ChatBloc, ChatState>(
|
||||
listener: (context, state) {
|
||||
cubit
|
||||
..updateSelectedSources(state.selectedSourceIds)
|
||||
..updateSelectedStatus();
|
||||
return AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(320, 380)),
|
||||
offset: const Offset(0.0, -10.0),
|
||||
direction: PopoverDirection.topWithCenterAligned,
|
||||
margin: EdgeInsets.zero,
|
||||
controller: popoverController,
|
||||
onOpen: () {
|
||||
context
|
||||
.read<ChatSettingsCubit>()
|
||||
.refreshSources(state.spaces, state.currentSpace);
|
||||
},
|
||||
child: AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(320, 380)),
|
||||
offset: const Offset(0.0, -10.0),
|
||||
direction: PopoverDirection.topWithCenterAligned,
|
||||
margin: EdgeInsets.zero,
|
||||
controller: popoverController,
|
||||
onOpen: () {
|
||||
context
|
||||
.read<ChatSettingsCubit>()
|
||||
.refreshSources(state.spaces, state.currentSpace);
|
||||
},
|
||||
onClose: () {
|
||||
widget.onUpdateSelectedSources(cubit.selectedSourceIds);
|
||||
context
|
||||
.read<ChatSettingsCubit>()
|
||||
.refreshSources(state.spaces, state.currentSpace);
|
||||
},
|
||||
popupBuilder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<ChatSettingsCubit>(),
|
||||
child: const _PopoverContent(),
|
||||
);
|
||||
},
|
||||
child: _IndicatorButton(
|
||||
onTap: () => popoverController.show(),
|
||||
),
|
||||
onClose: () {
|
||||
widget.onUpdateSelectedSources(cubit.selectedSourceIds);
|
||||
context
|
||||
.read<ChatSettingsCubit>()
|
||||
.refreshSources(state.spaces, state.currentSpace);
|
||||
},
|
||||
popupBuilder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<ChatSettingsCubit>(),
|
||||
child: const _PopoverContent(),
|
||||
);
|
||||
},
|
||||
child: _IndicatorButton(
|
||||
selectedSourcesNotifier: widget.selectedSourcesNotifier,
|
||||
onTap: () => popoverController.show(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onSelectedSourcesChanged() {
|
||||
cubit
|
||||
..updateSelectedSources(widget.selectedSourcesNotifier.value)
|
||||
..updateSelectedStatus();
|
||||
}
|
||||
}
|
||||
|
||||
class _IndicatorButton extends StatelessWidget {
|
||||
const _IndicatorButton({
|
||||
required this.selectedSourcesNotifier,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
@@ -144,11 +148,19 @@ class _IndicatorButton extends StatelessWidget {
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
const HSpace(2.0),
|
||||
BlocBuilder<ChatBloc, ChatState>(
|
||||
builder: (context, state) {
|
||||
ValueListenableBuilder(
|
||||
valueListenable: selectedSourcesNotifier,
|
||||
builder: (context, selectedSourceIds, _) {
|
||||
final documentId =
|
||||
context.read<DocumentBloc?>()?.documentId;
|
||||
final label = documentId != null &&
|
||||
selectedSourceIds.length == 1 &&
|
||||
selectedSourceIds[0] == documentId
|
||||
? LocaleKeys.chat_currentPage.tr()
|
||||
: selectedSourceIds.length.toString();
|
||||
return FlowyText(
|
||||
state.selectedSourceIds.length.toString(),
|
||||
fontSize: 14,
|
||||
label,
|
||||
fontSize: 12,
|
||||
figmaLineHeight: 16,
|
||||
color: Theme.of(context).hintColor,
|
||||
);
|
||||
|
||||
@@ -115,4 +115,9 @@ class KVKeys {
|
||||
///
|
||||
/// The value is a json string of [RecentIcons]
|
||||
static const String recentIcons = 'kRecentIcons';
|
||||
|
||||
/// The key for saving compact mode ids for node or databse view
|
||||
///
|
||||
/// The value is a json list of id
|
||||
static const String compactModeIds = 'compactModeIds';
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' as launcher;
|
||||
|
||||
typedef OnFailureCallback = void Function(Uri uri);
|
||||
@@ -38,6 +39,11 @@ Future<bool> afLaunchUri(
|
||||
);
|
||||
}
|
||||
|
||||
// on Linux, add http scheme to the url if it is not present
|
||||
if (UniversalPlatform.isLinux && !isURL(url, {'require_protocol': true})) {
|
||||
uri = Uri.parse('https://$url');
|
||||
}
|
||||
|
||||
// try to launch the uri directly
|
||||
bool result;
|
||||
try {
|
||||
|
||||
@@ -387,8 +387,8 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
bottom: 4.0,
|
||||
),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.lock_page_s,
|
||||
color: const Color(0xFFD95A0B),
|
||||
FlowySvgs.lock_page_fill_s,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+6
-2
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
|
||||
@@ -29,6 +27,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@@ -59,7 +58,9 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
late final PageController _pageController;
|
||||
|
||||
String get viewId => widget.databaseController.viewId;
|
||||
|
||||
RowCache get rowCache => widget.databaseController.rowCache;
|
||||
|
||||
FieldController get fieldController =>
|
||||
widget.databaseController.fieldController;
|
||||
|
||||
@@ -380,7 +381,9 @@ class MobileRowDetailPageContentState
|
||||
late final EditableCellBuilder cellBuilder;
|
||||
|
||||
String get viewId => widget.databaseController.viewId;
|
||||
|
||||
RowCache get rowCache => widget.databaseController.rowCache;
|
||||
|
||||
FieldController get fieldController =>
|
||||
widget.databaseController.fieldController;
|
||||
ValueNotifier<String> primaryFieldId = ValueNotifier('');
|
||||
@@ -542,6 +545,7 @@ class _TitleSkin extends IEditableTextCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TextCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
|
||||
+27
@@ -86,6 +86,15 @@ class MobileSelectionMenuStyle extends SelectionMenuStyle {
|
||||
required super.selectionMenuItemSelectedTextColor,
|
||||
required super.selectionMenuItemSelectedIconColor,
|
||||
required super.selectionMenuItemSelectedColor,
|
||||
required super.selectionMenuUnselectedLabelColor,
|
||||
required super.selectionMenuDividerColor,
|
||||
required super.selectionMenuLinkBorderColor,
|
||||
required super.selectionMenuInvalidLinkColor,
|
||||
required super.selectionMenuButtonColor,
|
||||
required super.selectionMenuButtonTextColor,
|
||||
required super.selectionMenuButtonIconColor,
|
||||
required super.selectionMenuButtonBorderColor,
|
||||
required super.selectionMenuTabIndicatorColor,
|
||||
required this.selectionMenuItemRightIconColor,
|
||||
});
|
||||
|
||||
@@ -99,6 +108,15 @@ class MobileSelectionMenuStyle extends SelectionMenuStyle {
|
||||
selectionMenuItemRightIconColor: Color(0xB31E2022),
|
||||
selectionMenuItemSelectedTextColor: Color.fromARGB(255, 56, 91, 247),
|
||||
selectionMenuItemSelectedIconColor: Color.fromARGB(255, 56, 91, 247),
|
||||
selectionMenuUnselectedLabelColor: Color(0xFF333333),
|
||||
selectionMenuDividerColor: Color(0xFF00BCF0),
|
||||
selectionMenuLinkBorderColor: Color(0xFF00BCF0),
|
||||
selectionMenuInvalidLinkColor: Color(0xFFE53935),
|
||||
selectionMenuButtonColor: Color(0xFF00BCF0),
|
||||
selectionMenuButtonTextColor: Color(0xFF333333),
|
||||
selectionMenuButtonIconColor: Color(0xFF333333),
|
||||
selectionMenuButtonBorderColor: Color(0xFF00BCF0),
|
||||
selectionMenuTabIndicatorColor: Color(0xFF00BCF0),
|
||||
);
|
||||
|
||||
static const MobileSelectionMenuStyle dark = MobileSelectionMenuStyle(
|
||||
@@ -109,5 +127,14 @@ class MobileSelectionMenuStyle extends SelectionMenuStyle {
|
||||
selectionMenuItemRightIconColor: Color(0xB3FFFFFF),
|
||||
selectionMenuItemSelectedTextColor: Color(0xFF131720),
|
||||
selectionMenuItemSelectedIconColor: Color(0xFF131720),
|
||||
selectionMenuUnselectedLabelColor: Color(0xFFBBC3CD),
|
||||
selectionMenuDividerColor: Color(0xFF3A3F44),
|
||||
selectionMenuLinkBorderColor: Color(0xFF3A3F44),
|
||||
selectionMenuInvalidLinkColor: Color(0xFFE53935),
|
||||
selectionMenuButtonColor: Color(0xFF00BCF0),
|
||||
selectionMenuButtonTextColor: Color(0xFFFFFFFF),
|
||||
selectionMenuButtonIconColor: Color(0xFFFFFFFF),
|
||||
selectionMenuButtonBorderColor: Color(0xFF00BCF0),
|
||||
selectionMenuTabIndicatorColor: Color(0xFF00BCF0),
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/util/int64_extension.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@@ -10,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -28,6 +30,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
required this.userId,
|
||||
}) : chatController = InMemoryChatController(),
|
||||
listener = ChatMessageListener(chatId: chatId),
|
||||
selectedSourcesNotifier = ValueNotifier([]),
|
||||
super(ChatState.initial()) {
|
||||
_startListening();
|
||||
_dispatch();
|
||||
@@ -38,7 +41,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
final String chatId;
|
||||
final String userId;
|
||||
final ChatMessageListener listener;
|
||||
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final ChatController chatController;
|
||||
|
||||
/// The last streaming message id
|
||||
@@ -68,7 +71,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
await listener.stop();
|
||||
final request = ViewIdPB(value: chatId);
|
||||
unawaited(FolderEventCloseView(request).send());
|
||||
|
||||
selectedSourcesNotifier.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@@ -251,12 +254,10 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
);
|
||||
},
|
||||
didReceiveChatSettings: (settings) {
|
||||
emit(
|
||||
state.copyWith(selectedSourceIds: settings.ragIds),
|
||||
);
|
||||
selectedSourcesNotifier.value = settings.ragIds;
|
||||
},
|
||||
updateSelectedSources: (selectedSourcesIds) async {
|
||||
emit(state.copyWith(selectedSourceIds: selectedSourcesIds));
|
||||
selectedSourcesNotifier.value = [...selectedSourcesIds];
|
||||
|
||||
final payload = UpdateChatSettingsPB(
|
||||
chatId: ChatId(value: chatId),
|
||||
@@ -665,14 +666,12 @@ class ChatEvent with _$ChatEvent {
|
||||
@freezed
|
||||
class ChatState with _$ChatState {
|
||||
const factory ChatState({
|
||||
required List<String> selectedSourceIds,
|
||||
required LoadChatMessageStatus loadingState,
|
||||
required PromptResponseState promptResponseState,
|
||||
required bool clearErrorMessages,
|
||||
}) = _ChatState;
|
||||
|
||||
factory ChatState.initial() => const ChatState(
|
||||
selectedSourceIds: [],
|
||||
loadingState: LoadChatMessageStatus.loading,
|
||||
promptResponseState: PromptResponseState.ready,
|
||||
clearErrorMessages: false,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
@@ -140,87 +137,3 @@ enum LoadChatMessageStatus {
|
||||
loadingRemote,
|
||||
ready,
|
||||
}
|
||||
|
||||
class PredefinedFormat extends Equatable {
|
||||
const PredefinedFormat({
|
||||
required this.imageFormat,
|
||||
required this.textFormat,
|
||||
});
|
||||
|
||||
const PredefinedFormat.auto()
|
||||
: imageFormat = ImageFormat.text,
|
||||
textFormat = TextFormat.paragraph;
|
||||
|
||||
final ImageFormat imageFormat;
|
||||
final TextFormat? textFormat;
|
||||
|
||||
PredefinedFormatPB toPB() {
|
||||
return PredefinedFormatPB(
|
||||
imageFormat: switch (imageFormat) {
|
||||
ImageFormat.text => ResponseImageFormatPB.TextOnly,
|
||||
ImageFormat.image => ResponseImageFormatPB.ImageOnly,
|
||||
ImageFormat.textAndImage => ResponseImageFormatPB.TextAndImage,
|
||||
},
|
||||
textFormat: switch (textFormat) {
|
||||
TextFormat.paragraph => ResponseTextFormatPB.Paragraph,
|
||||
TextFormat.bulletList => ResponseTextFormatPB.BulletedList,
|
||||
TextFormat.numberedList => ResponseTextFormatPB.NumberedList,
|
||||
TextFormat.table => ResponseTextFormatPB.Table,
|
||||
_ => null,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [imageFormat, textFormat];
|
||||
}
|
||||
|
||||
enum ImageFormat {
|
||||
text,
|
||||
image,
|
||||
textAndImage;
|
||||
|
||||
bool get hasText => this == text || this == textAndImage;
|
||||
|
||||
FlowySvgData get icon {
|
||||
return switch (this) {
|
||||
ImageFormat.text => FlowySvgs.ai_text_s,
|
||||
ImageFormat.image => FlowySvgs.ai_image_s,
|
||||
ImageFormat.textAndImage => FlowySvgs.ai_text_image_s,
|
||||
};
|
||||
}
|
||||
|
||||
String get i18n {
|
||||
return switch (this) {
|
||||
ImageFormat.text => LocaleKeys.chat_changeFormat_textOnly.tr(),
|
||||
ImageFormat.image => LocaleKeys.chat_changeFormat_imageOnly.tr(),
|
||||
ImageFormat.textAndImage =>
|
||||
LocaleKeys.chat_changeFormat_textAndImage.tr(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum TextFormat {
|
||||
paragraph,
|
||||
bulletList,
|
||||
numberedList,
|
||||
table;
|
||||
|
||||
FlowySvgData get icon {
|
||||
return switch (this) {
|
||||
TextFormat.paragraph => FlowySvgs.ai_paragraph_s,
|
||||
TextFormat.bulletList => FlowySvgs.ai_list_s,
|
||||
TextFormat.numberedList => FlowySvgs.ai_number_list_s,
|
||||
TextFormat.table => FlowySvgs.ai_table_s,
|
||||
};
|
||||
}
|
||||
|
||||
String get i18n {
|
||||
return switch (this) {
|
||||
TextFormat.paragraph => LocaleKeys.chat_changeFormat_text.tr(),
|
||||
TextFormat.bulletList => LocaleKeys.chat_changeFormat_bullet.tr(),
|
||||
TextFormat.numberedList => LocaleKeys.chat_changeFormat_number.tr(),
|
||||
TextFormat.table => LocaleKeys.chat_changeFormat_table.tr(),
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -113,9 +113,8 @@ class ChatSettingsCubit extends Cubit<ChatSettingsState> {
|
||||
String filter = '';
|
||||
|
||||
void updateSelectedSources(List<String> newSelectedSourceIds) {
|
||||
selectedSourceIds
|
||||
..clear
|
||||
..addAll(newSelectedSourceIds);
|
||||
selectedSourceIds.clear();
|
||||
selectedSourceIds.addAll(newSelectedSourceIds);
|
||||
}
|
||||
|
||||
void refreshSources(List<ViewPB> spaceViews, ViewPB? currentSpace) async {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_message_selector_banner.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
@@ -19,14 +20,12 @@ import 'package:string_validator/string_validator.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'application/ai_prompt_input_bloc.dart';
|
||||
import 'application/chat_bloc.dart';
|
||||
import 'application/chat_entity.dart';
|
||||
import 'application/chat_member_bloc.dart';
|
||||
import 'application/chat_select_message_bloc.dart';
|
||||
import 'application/chat_message_stream.dart';
|
||||
import 'presentation/animated_chat_list.dart';
|
||||
import 'presentation/chat_input/desktop_chat_input.dart';
|
||||
import 'presentation/chat_input/mobile_chat_input.dart';
|
||||
import 'presentation/chat_related_question.dart';
|
||||
import 'presentation/chat_welcome_page.dart';
|
||||
@@ -71,7 +70,14 @@ class AIChatPage extends StatelessWidget {
|
||||
),
|
||||
|
||||
/// [AIPromptInputBloc] is used to handle the user prompt
|
||||
BlocProvider(create: (_) => AIPromptInputBloc()),
|
||||
BlocProvider(
|
||||
create: (_) => AIPromptInputBloc(
|
||||
predefinedFormat: PredefinedFormat(
|
||||
imageFormat: ImageFormat.text,
|
||||
textFormat: TextFormat.bulletList,
|
||||
),
|
||||
),
|
||||
),
|
||||
BlocProvider(create: (_) => ChatMemberBloc()),
|
||||
],
|
||||
child: Builder(
|
||||
@@ -146,7 +152,7 @@ class _ChatContentPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
_wrapConstraints(
|
||||
_builtInput(context),
|
||||
_Input(view: view),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -184,26 +190,25 @@ class _ChatContentPage extends StatelessWidget {
|
||||
return RelatedQuestionList(
|
||||
relatedQuestions: message.metadata!['questions'],
|
||||
onQuestionSelected: (question) {
|
||||
context
|
||||
.read<ChatBloc>()
|
||||
.add(ChatEvent.sendMessage(message: question));
|
||||
final bloc = context.read<AIPromptInputBloc>();
|
||||
final showPredefinedFormats = bloc.state.showPredefinedFormats;
|
||||
final predefinedFormat = bloc.state.predefinedFormat;
|
||||
|
||||
context.read<ChatBloc>().add(
|
||||
ChatEvent.sendMessage(
|
||||
message: question,
|
||||
format: showPredefinedFormats ? predefinedFormat : null,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (message.author.id == userProfile.id.toString()) {
|
||||
if (message.author.id == userProfile.id.toString() ||
|
||||
isOtherUserMessage(message)) {
|
||||
return ChatUserMessageWidget(
|
||||
user: message.author,
|
||||
message: message,
|
||||
isCurrentUser: true,
|
||||
);
|
||||
}
|
||||
|
||||
if (isOtherUserMessage(message)) {
|
||||
return ChatUserMessageWidget(
|
||||
user: message.author,
|
||||
message: message,
|
||||
isCurrentUser: false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -286,7 +291,16 @@ class _ChatContentPage extends StatelessWidget {
|
||||
return ChatWelcomePage(
|
||||
userProfile: userProfile,
|
||||
onSelectedQuestion: (question) {
|
||||
bloc.add(ChatEvent.sendMessage(message: question));
|
||||
final aiPromptInputBloc = context.read<AIPromptInputBloc>();
|
||||
final showPredefinedFormats =
|
||||
aiPromptInputBloc.state.showPredefinedFormats;
|
||||
final predefinedFormat = aiPromptInputBloc.state.predefinedFormat;
|
||||
bloc.add(
|
||||
ChatEvent.sendMessage(
|
||||
message: question,
|
||||
format: showPredefinedFormats ? predefinedFormat : null,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -308,86 +322,6 @@ class _ChatContentPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _builtInput(BuildContext context) {
|
||||
return BlocSelector<ChatSelectMessageBloc, ChatSelectMessageState, bool>(
|
||||
selector: (state) => state.isSelectingMessages,
|
||||
builder: (context, isSelectingMessages) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) {
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axisAlignment: -1,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: isSelectingMessages
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: AIChatUILayout.safeAreaInsets(context),
|
||||
child: BlocSelector<ChatBloc, ChatState, bool>(
|
||||
selector: (state) {
|
||||
return state.promptResponseState ==
|
||||
PromptResponseState.ready;
|
||||
},
|
||||
builder: (context, canSendMessage) {
|
||||
final chatBloc = context.read<ChatBloc>();
|
||||
|
||||
return UniversalPlatform.isDesktop
|
||||
? DesktopChatInput(
|
||||
chatId: view.id,
|
||||
isStreaming: !canSendMessage,
|
||||
onStopStreaming: () {
|
||||
chatBloc.add(const ChatEvent.stopStream());
|
||||
},
|
||||
onSubmitted: (text, format, metadata) {
|
||||
chatBloc.add(
|
||||
ChatEvent.sendMessage(
|
||||
message: text,
|
||||
format: format,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
},
|
||||
onUpdateSelectedSources: (ids) {
|
||||
chatBloc.add(
|
||||
ChatEvent.updateSelectedSources(
|
||||
selectedSourcesIds: ids,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: MobileChatInput(
|
||||
chatId: view.id,
|
||||
isStreaming: !canSendMessage,
|
||||
onStopStreaming: () {
|
||||
chatBloc.add(const ChatEvent.stopStream());
|
||||
},
|
||||
onSubmitted: (text, format, metadata) {
|
||||
chatBloc.add(
|
||||
ChatEvent.sendMessage(
|
||||
message: text,
|
||||
format: format,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
},
|
||||
onUpdateSelectedSources: (ids) {
|
||||
chatBloc.add(
|
||||
ChatEvent.updateSelectedSources(
|
||||
selectedSourcesIds: ids,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onSelectMetadata(
|
||||
BuildContext context,
|
||||
ChatMessageRefSource metadata,
|
||||
@@ -414,3 +348,94 @@ class _ChatContentPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _Input extends StatelessWidget {
|
||||
const _Input({
|
||||
required this.view,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<ChatSelectMessageBloc, ChatSelectMessageState, bool>(
|
||||
selector: (state) => state.isSelectingMessages,
|
||||
builder: (context, isSelectingMessages) {
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
transitionBuilder: (child, animation) {
|
||||
return SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axisAlignment: -1,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: isSelectingMessages
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: AIChatUILayout.safeAreaInsets(context),
|
||||
child: BlocSelector<ChatBloc, ChatState, bool>(
|
||||
selector: (state) {
|
||||
return state.promptResponseState ==
|
||||
PromptResponseState.ready;
|
||||
},
|
||||
builder: (context, canSendMessage) {
|
||||
final chatBloc = context.read<ChatBloc>();
|
||||
|
||||
return UniversalPlatform.isDesktop
|
||||
? DesktopPromptInput(
|
||||
isStreaming: !canSendMessage,
|
||||
onStopStreaming: () {
|
||||
chatBloc.add(const ChatEvent.stopStream());
|
||||
},
|
||||
onSubmitted: (text, format, metadata) {
|
||||
chatBloc.add(
|
||||
ChatEvent.sendMessage(
|
||||
message: text,
|
||||
format: format,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
},
|
||||
selectedSourcesNotifier:
|
||||
chatBloc.selectedSourcesNotifier,
|
||||
onUpdateSelectedSources: (ids) {
|
||||
chatBloc.add(
|
||||
ChatEvent.updateSelectedSources(
|
||||
selectedSourcesIds: ids,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
: MobileChatInput(
|
||||
isStreaming: !canSendMessage,
|
||||
onStopStreaming: () {
|
||||
chatBloc.add(const ChatEvent.stopStream());
|
||||
},
|
||||
onSubmitted: (text, format, metadata) {
|
||||
chatBloc.add(
|
||||
ChatEvent.sendMessage(
|
||||
message: text,
|
||||
format: format,
|
||||
metadata: metadata,
|
||||
),
|
||||
);
|
||||
},
|
||||
selectedSourcesNotifier:
|
||||
chatBloc.selectedSourcesNotifier,
|
||||
onUpdateSelectedSources: (ids) {
|
||||
chatBloc.add(
|
||||
ChatEvent.updateSelectedSources(
|
||||
selectedSourcesIds: ids,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+81
-67
@@ -1,8 +1,7 @@
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/ai_prompt_input_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:extended_text_field/extended_text_field.dart';
|
||||
@@ -10,21 +9,19 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../layout_define.dart';
|
||||
|
||||
class MobileChatInput extends StatefulWidget {
|
||||
const MobileChatInput({
|
||||
super.key,
|
||||
required this.chatId,
|
||||
required this.isStreaming,
|
||||
required this.onStopStreaming,
|
||||
required this.onSubmitted,
|
||||
required this.selectedSourcesNotifier,
|
||||
required this.onUpdateSelectedSources,
|
||||
});
|
||||
|
||||
final String chatId;
|
||||
final bool isStreaming;
|
||||
final void Function() onStopStreaming;
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final void Function(String, PredefinedFormat?, Map<String, dynamic>)
|
||||
onSubmitted;
|
||||
final void Function(List<String>) onUpdateSelectedSources;
|
||||
@@ -38,11 +35,6 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
final focusNode = FocusNode();
|
||||
final textController = TextEditingController();
|
||||
|
||||
bool showPredefinedFormatSection = true;
|
||||
PredefinedFormat predefinedFormat = const PredefinedFormat(
|
||||
imageFormat: ImageFormat.text,
|
||||
textFormat: TextFormat.bulletList,
|
||||
);
|
||||
late SendButtonState sendButtonState;
|
||||
|
||||
@override
|
||||
@@ -106,53 +98,62 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
borderRadius:
|
||||
const BorderRadius.vertical(top: Radius.circular(8.0)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight:
|
||||
MobileAIPromptSizes.attachedFilesBarPadding.vertical +
|
||||
child: BlocBuilder<AIPromptInputBloc, AIPromptInputState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MobileAIPromptSizes
|
||||
.attachedFilesBarPadding.vertical +
|
||||
MobileAIPromptSizes.attachedFilesPreviewHeight,
|
||||
),
|
||||
child: PromptInputFile(
|
||||
chatId: widget.chatId,
|
||||
onDeleted: (file) => context
|
||||
.read<AIPromptInputBloc>()
|
||||
.add(AIPromptInputEvent.removeFile(file)),
|
||||
),
|
||||
),
|
||||
if (showPredefinedFormatSection)
|
||||
TextFieldTapRegion(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ChangeFormatBar(
|
||||
predefinedFormat: predefinedFormat,
|
||||
spacing: 8.0,
|
||||
onSelectPredefinedFormat: (format) {
|
||||
setState(() => predefinedFormat = format);
|
||||
},
|
||||
),
|
||||
child: PromptInputFile(
|
||||
onDeleted: (file) => context
|
||||
.read<AIPromptInputBloc>()
|
||||
.add(AIPromptInputEvent.removeFile(file)),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
const VSpace(8.0),
|
||||
inputTextField(context),
|
||||
TextFieldTapRegion(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(8.0),
|
||||
leadingButtons(context),
|
||||
const Spacer(),
|
||||
sendButton(),
|
||||
const HSpace(12.0),
|
||||
],
|
||||
if (state.showPredefinedFormats)
|
||||
TextFieldTapRegion(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ChangeFormatBar(
|
||||
predefinedFormat: state.predefinedFormat,
|
||||
spacing: 8.0,
|
||||
onSelectPredefinedFormat: (format) =>
|
||||
context.read<AIPromptInputBloc>().add(
|
||||
AIPromptInputEvent.updatePredefinedFormat(
|
||||
format,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
const VSpace(8.0),
|
||||
inputTextField(context),
|
||||
TextFieldTapRegion(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(8.0),
|
||||
leadingButtons(
|
||||
context,
|
||||
state.showPredefinedFormats,
|
||||
),
|
||||
const Spacer(),
|
||||
sendButton(),
|
||||
const HSpace(12.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -185,9 +186,13 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
// get the attached files and mentioned pages
|
||||
final metadata = context.read<AIPromptInputBloc>().consumeMetadata();
|
||||
|
||||
final bloc = context.read<AIPromptInputBloc>();
|
||||
final showPredefinedFormats = bloc.state.showPredefinedFormats;
|
||||
final predefinedFormat = bloc.state.predefinedFormat;
|
||||
|
||||
widget.onSubmitted(
|
||||
trimmedText,
|
||||
showPredefinedFormatSection ? predefinedFormat : null,
|
||||
showPredefinedFormats ? predefinedFormat : null,
|
||||
metadata,
|
||||
);
|
||||
}
|
||||
@@ -266,7 +271,7 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
AIType.appflowyAI => LocaleKeys.chat_inputMessageHint.tr(),
|
||||
AIType.localAI => LocaleKeys.chat_inputLocalAIMessageHint.tr()
|
||||
},
|
||||
hintStyle: AIChatUILayout.inputHintTextStyle(context),
|
||||
hintStyle: inputHintTextStyle(context),
|
||||
isCollapsed: true,
|
||||
isDense: true,
|
||||
),
|
||||
@@ -289,7 +294,15 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget leadingButtons(BuildContext context) {
|
||||
TextStyle? inputHintTextStyle(BuildContext context) {
|
||||
return Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).isLightMode
|
||||
? const Color(0xFFBDC2C8)
|
||||
: const Color(0xFF3C3E51),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leadingButtons(BuildContext context, bool showPredefinedFormats) {
|
||||
return _LeadingActions(
|
||||
// onMention: () {
|
||||
// textController.text += '@';
|
||||
@@ -300,13 +313,13 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
// mentionPage(context);
|
||||
// });
|
||||
// },
|
||||
showPredefinedFormatSection: showPredefinedFormatSection,
|
||||
predefinedFormat: predefinedFormat,
|
||||
showPredefinedFormats: showPredefinedFormats,
|
||||
onTogglePredefinedFormatSection: () {
|
||||
setState(() {
|
||||
showPredefinedFormatSection = !showPredefinedFormatSection;
|
||||
});
|
||||
context
|
||||
.read<AIPromptInputBloc>()
|
||||
.add(AIPromptInputEvent.toggleShowPredefinedFormat());
|
||||
},
|
||||
selectedSourcesNotifier: widget.selectedSourcesNotifier,
|
||||
onUpdateSelectedSources: widget.onUpdateSelectedSources,
|
||||
);
|
||||
}
|
||||
@@ -322,15 +335,15 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
|
||||
class _LeadingActions extends StatelessWidget {
|
||||
const _LeadingActions({
|
||||
required this.showPredefinedFormatSection,
|
||||
required this.predefinedFormat,
|
||||
required this.showPredefinedFormats,
|
||||
required this.onTogglePredefinedFormatSection,
|
||||
required this.selectedSourcesNotifier,
|
||||
required this.onUpdateSelectedSources,
|
||||
});
|
||||
|
||||
final bool showPredefinedFormatSection;
|
||||
final PredefinedFormat predefinedFormat;
|
||||
final bool showPredefinedFormats;
|
||||
final void Function() onTogglePredefinedFormatSection;
|
||||
final ValueNotifier<List<String>> selectedSourcesNotifier;
|
||||
final void Function(List<String>) onUpdateSelectedSources;
|
||||
|
||||
@override
|
||||
@@ -342,10 +355,11 @@ class _LeadingActions extends StatelessWidget {
|
||||
separatorBuilder: () => const HSpace(4.0),
|
||||
children: [
|
||||
PromptInputMobileSelectSourcesButton(
|
||||
selectedSourcesNotifier: selectedSourcesNotifier,
|
||||
onUpdateSelectedSources: onUpdateSelectedSources,
|
||||
),
|
||||
PromptInputMobileToggleFormatButton(
|
||||
showFormatBar: showPredefinedFormatSection,
|
||||
showFormatBar: showPredefinedFormats,
|
||||
onTap: onTogglePredefinedFormatSection,
|
||||
),
|
||||
],
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ class RelatedQuestionItem extends StatelessWidget {
|
||||
child: FlowyText(
|
||||
question,
|
||||
lineHeight: 1.4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: null,
|
||||
),
|
||||
),
|
||||
expandText: false,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
@@ -20,14 +19,6 @@ class AIChatUILayout {
|
||||
static EdgeInsets get messageMargin => UniversalPlatform.isMobile
|
||||
? const EdgeInsets.symmetric(horizontal: 16)
|
||||
: EdgeInsets.zero;
|
||||
|
||||
static TextStyle? inputHintTextStyle(BuildContext context) {
|
||||
return Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).isLightMode
|
||||
? const Color(0xFFBDC2C8)
|
||||
: const Color(0xFF3C3E51),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DesktopAIChatSizes {
|
||||
|
||||
+1
-1
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
+2
-5
@@ -1,14 +1,11 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/ai/widgets/prompt_input/layout_define.dart';
|
||||
import 'package:appflowy/ai/widgets/prompt_input/predefined_format_buttons.dart';
|
||||
import 'package:appflowy/ai/widgets/prompt_input/select_sources_menu.dart';
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_edit_document_service.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
@@ -220,7 +217,7 @@ class RegenerateButton extends StatelessWidget {
|
||||
? DesktopAIChatSizes.messageHoverActionBarIconRadius
|
||||
: DesktopAIChatSizes.messageActionBarIconRadius,
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.ai_undo_s,
|
||||
FlowySvgs.ai_try_again_s,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: const Size.square(16),
|
||||
),
|
||||
|
||||
+5
-4
@@ -1,11 +1,11 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_edit_document_service.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_select_message_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
@@ -22,7 +22,6 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../chat_avatar.dart';
|
||||
import '../../../../ai/widgets/prompt_input/mention_page_bottom_sheet.dart';
|
||||
import '../layout_define.dart';
|
||||
import 'ai_message_action_bar.dart';
|
||||
import 'ai_change_format_bottom_sheet.dart';
|
||||
@@ -377,7 +376,7 @@ class ChatAIMessagePopup extends StatelessWidget {
|
||||
onRegenerate?.call();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: FlowySvgs.ai_undo_s,
|
||||
icon: FlowySvgs.ai_try_again_s,
|
||||
iconSize: const Size.square(20),
|
||||
text: LocaleKeys.chat_regenerate.tr(),
|
||||
);
|
||||
@@ -470,7 +469,9 @@ class _WrapIsSelectingMessage extends StatelessWidget {
|
||||
if (isSelectingMessages)
|
||||
ChatSelectMessageIndicator(isSelected: isSelected)
|
||||
else
|
||||
const ChatAIAvatar(),
|
||||
SelectionContainer.disabled(
|
||||
child: const ChatAIAvatar(),
|
||||
),
|
||||
const HSpace(DesktopAIChatSizes.avatarAndChatBubbleSpacing),
|
||||
Expanded(
|
||||
child: IgnorePointer(
|
||||
|
||||
+14
-6
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/ai/widgets/loading_indicator.dart';
|
||||
import 'package:appflowy/ai/ai.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
|
||||
@@ -84,14 +84,20 @@ class ChatAIMessageWidget extends StatelessWidget {
|
||||
loading: () => ChatAIMessageBubble(
|
||||
message: message,
|
||||
showActions: false,
|
||||
child: AILoadingIndicator(text: loadingText),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: AILoadingIndicator(text: loadingText),
|
||||
),
|
||||
),
|
||||
ready: () {
|
||||
return state.text.isEmpty
|
||||
? ChatAIMessageBubble(
|
||||
message: message,
|
||||
showActions: false,
|
||||
child: AILoadingIndicator(text: loadingText),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: AILoadingIndicator(text: loadingText),
|
||||
),
|
||||
)
|
||||
: ChatAIMessageBubble(
|
||||
message: message,
|
||||
@@ -107,9 +113,11 @@ class ChatAIMessageWidget extends StatelessWidget {
|
||||
children: [
|
||||
AIMarkdownText(markdown: state.text),
|
||||
if (state.sources.isNotEmpty)
|
||||
AIMessageMetadata(
|
||||
sources: state.sources,
|
||||
onSelectedMetadata: onSelectedMetadata,
|
||||
SelectionContainer.disabled(
|
||||
child: AIMessageMetadata(
|
||||
sources: state.sources,
|
||||
onSelectedMetadata: onSelectedMetadata,
|
||||
),
|
||||
),
|
||||
if (state.sources.isNotEmpty && !isLastMessage)
|
||||
const VSpace(8.0),
|
||||
|
||||
+11
-25
@@ -15,13 +15,11 @@ class ChatUserMessageBubble extends StatelessWidget {
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.child,
|
||||
required this.isCurrentUser,
|
||||
this.files = const [],
|
||||
});
|
||||
|
||||
final Message message;
|
||||
final Widget child;
|
||||
final bool isCurrentUser;
|
||||
final List<ChatFile> files;
|
||||
|
||||
@override
|
||||
@@ -44,40 +42,28 @@ class ChatUserMessageBubble extends StatelessWidget {
|
||||
const VSpace(6),
|
||||
],
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: getChildren(context),
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
_buildBubble(context),
|
||||
const HSpace(DesktopAIChatSizes.avatarAndChatBubbleSpacing),
|
||||
_buildAvatar(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> getChildren(BuildContext context) {
|
||||
if (isCurrentUser) {
|
||||
return [
|
||||
const Spacer(),
|
||||
_buildBubble(context),
|
||||
const HSpace(DesktopAIChatSizes.avatarAndChatBubbleSpacing),
|
||||
_buildAvatar(),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
_buildAvatar(),
|
||||
const HSpace(DesktopAIChatSizes.avatarAndChatBubbleSpacing),
|
||||
_buildBubble(context),
|
||||
const Spacer(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildAvatar() {
|
||||
return BlocBuilder<ChatMemberBloc, ChatMemberState>(
|
||||
builder: (context, state) {
|
||||
final member = state.members[message.author.id];
|
||||
return ChatUserAvatar(
|
||||
iconUrl: member?.info.avatarUrl ?? "",
|
||||
name: member?.info.name ?? "",
|
||||
return SelectionContainer.disabled(
|
||||
child: ChatUserAvatar(
|
||||
iconUrl: member?.info.avatarUrl ?? "",
|
||||
name: member?.info.name ?? "",
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
-3
@@ -15,12 +15,10 @@ class ChatUserMessageWidget extends StatelessWidget {
|
||||
super.key,
|
||||
required this.user,
|
||||
required this.message,
|
||||
required this.isCurrentUser,
|
||||
});
|
||||
|
||||
final User user;
|
||||
final TextMessage message;
|
||||
final bool isCurrentUser;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -34,7 +32,6 @@ class ChatUserMessageWidget extends StatelessWidget {
|
||||
),
|
||||
child: ChatUserMessageBubble(
|
||||
message: message,
|
||||
isCurrentUser: isCurrentUser,
|
||||
files: _getFiles(),
|
||||
child: BlocBuilder<ChatUserMessageBloc, ChatUserMessageState>(
|
||||
builder: (context, state) {
|
||||
|
||||
+34
-6
@@ -1,7 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/view/view_cache.dart';
|
||||
import 'package:appflowy/plugins/database/domain/database_view_service.dart';
|
||||
@@ -14,6 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'defines.dart';
|
||||
import 'row/row_cache.dart';
|
||||
@@ -41,7 +40,9 @@ class GroupCallbacks {
|
||||
}
|
||||
|
||||
class DatabaseLayoutSettingCallbacks {
|
||||
DatabaseLayoutSettingCallbacks({required this.onLayoutSettingsChanged});
|
||||
DatabaseLayoutSettingCallbacks({
|
||||
required this.onLayoutSettingsChanged,
|
||||
});
|
||||
|
||||
final void Function(DatabaseLayoutSettingPB) onLayoutSettingsChanged;
|
||||
}
|
||||
@@ -97,9 +98,11 @@ class DatabaseController {
|
||||
final List<DatabaseCallbacks> _databaseCallbacks = [];
|
||||
final List<GroupCallbacks> _groupCallbacks = [];
|
||||
final List<DatabaseLayoutSettingCallbacks> _layoutCallbacks = [];
|
||||
final Set<ValueChanged<bool>> _compactModeCallbacks = {};
|
||||
|
||||
// Getters
|
||||
RowCache get rowCache => _viewCache.rowCache;
|
||||
|
||||
String get viewId => view.id;
|
||||
|
||||
// Listener
|
||||
@@ -107,17 +110,26 @@ class DatabaseController {
|
||||
final DatabaseLayoutSettingListener _layoutListener;
|
||||
|
||||
final ValueNotifier<bool> _isLoading = ValueNotifier(true);
|
||||
final ValueNotifier<bool> _compactMode = ValueNotifier(true);
|
||||
|
||||
void setIsLoading(bool isLoading) {
|
||||
_isLoading.value = isLoading;
|
||||
}
|
||||
void setIsLoading(bool isLoading) => _isLoading.value = isLoading;
|
||||
|
||||
ValueNotifier<bool> get isLoading => _isLoading;
|
||||
|
||||
void setCompactMode(bool compactMode) {
|
||||
_compactMode.value = compactMode;
|
||||
for (final callback in Set.of(_compactModeCallbacks)) {
|
||||
callback.call(compactMode);
|
||||
}
|
||||
}
|
||||
|
||||
ValueNotifier<bool> get compactModeNotifier => _compactMode;
|
||||
|
||||
void addListener({
|
||||
DatabaseCallbacks? onDatabaseChanged,
|
||||
DatabaseLayoutSettingCallbacks? onLayoutSettingsChanged,
|
||||
GroupCallbacks? onGroupChanged,
|
||||
ValueChanged<bool>? onCompactModeChanged,
|
||||
}) {
|
||||
if (onLayoutSettingsChanged != null) {
|
||||
_layoutCallbacks.add(onLayoutSettingsChanged);
|
||||
@@ -130,12 +142,17 @@ class DatabaseController {
|
||||
if (onGroupChanged != null) {
|
||||
_groupCallbacks.add(onGroupChanged);
|
||||
}
|
||||
|
||||
if (onCompactModeChanged != null) {
|
||||
_compactModeCallbacks.add(onCompactModeChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener({
|
||||
DatabaseCallbacks? onDatabaseChanged,
|
||||
DatabaseLayoutSettingCallbacks? onLayoutSettingsChanged,
|
||||
GroupCallbacks? onGroupChanged,
|
||||
ValueChanged<bool>? onCompactModeChanged,
|
||||
}) {
|
||||
if (onDatabaseChanged != null) {
|
||||
_databaseCallbacks.remove(onDatabaseChanged);
|
||||
@@ -148,6 +165,10 @@ class DatabaseController {
|
||||
if (onGroupChanged != null) {
|
||||
_groupCallbacks.remove(onGroupChanged);
|
||||
}
|
||||
|
||||
if (onCompactModeChanged != null) {
|
||||
_compactModeCallbacks.remove(onCompactModeChanged);
|
||||
}
|
||||
}
|
||||
|
||||
Future<FlowyResult<void, FlowyError>> open() async {
|
||||
@@ -242,6 +263,7 @@ class DatabaseController {
|
||||
_databaseCallbacks.clear();
|
||||
_groupCallbacks.clear();
|
||||
_layoutCallbacks.clear();
|
||||
_compactModeCallbacks.clear();
|
||||
_isLoading.dispose();
|
||||
}
|
||||
|
||||
@@ -376,4 +398,10 @@ class DatabaseController {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void initCompactMode(bool enableCompactMode) {
|
||||
if (_compactMode.value != enableCompactMode) {
|
||||
_compactMode.value = enableCompactMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ class DatabaseLayoutBloc
|
||||
@freezed
|
||||
class DatabaseLayoutEvent with _$DatabaseLayoutEvent {
|
||||
const factory DatabaseLayoutEvent.initial() = _Initial;
|
||||
|
||||
const factory DatabaseLayoutEvent.updateLayout(DatabaseLayoutPB layout) =
|
||||
_UpdateLayout;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/plugins/database/domain/database_view_service.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/compact_mode_event.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@@ -17,8 +18,17 @@ part 'tab_bar_bloc.freezed.dart';
|
||||
|
||||
class DatabaseTabBarBloc
|
||||
extends Bloc<DatabaseTabBarEvent, DatabaseTabBarState> {
|
||||
DatabaseTabBarBloc({required ViewPB view})
|
||||
: super(DatabaseTabBarState.initial(view)) {
|
||||
DatabaseTabBarBloc({
|
||||
required ViewPB view,
|
||||
required String compactModeId,
|
||||
required bool enableCompactMode,
|
||||
}) : super(
|
||||
DatabaseTabBarState.initial(
|
||||
view,
|
||||
compactModeId,
|
||||
enableCompactMode,
|
||||
),
|
||||
) {
|
||||
on<DatabaseTabBarEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@@ -154,10 +164,13 @@ class DatabaseTabBarBloc
|
||||
) {
|
||||
final tabBarControllerByViewId = {...state.tabBarControllerByViewId};
|
||||
for (final view in newViews) {
|
||||
final controller = DatabaseTabBarController(view: view);
|
||||
controller.onViewUpdated = (newView) {
|
||||
add(DatabaseTabBarEvent.viewDidUpdate(newView));
|
||||
};
|
||||
final controller = DatabaseTabBarController(
|
||||
view: view,
|
||||
compactModeId: state.compactModeId,
|
||||
enableCompactMode: state.enableCompactMode,
|
||||
)..onViewUpdated = (newView) {
|
||||
add(DatabaseTabBarEvent.viewDidUpdate(newView));
|
||||
};
|
||||
|
||||
tabBarControllerByViewId[view.id] = controller;
|
||||
}
|
||||
@@ -205,20 +218,27 @@ class DatabaseTabBarBloc
|
||||
@freezed
|
||||
class DatabaseTabBarEvent with _$DatabaseTabBarEvent {
|
||||
const factory DatabaseTabBarEvent.initial() = _Initial;
|
||||
|
||||
const factory DatabaseTabBarEvent.didLoadChildViews(
|
||||
List<ViewPB> childViews,
|
||||
) = _DidLoadChildViews;
|
||||
|
||||
const factory DatabaseTabBarEvent.selectView(String viewId) = _DidSelectView;
|
||||
|
||||
const factory DatabaseTabBarEvent.createView(
|
||||
DatabaseLayoutPB layout,
|
||||
String? name,
|
||||
) = _CreateView;
|
||||
|
||||
const factory DatabaseTabBarEvent.renameView(String viewId, String newName) =
|
||||
_RenameView;
|
||||
|
||||
const factory DatabaseTabBarEvent.deleteView(String viewId) = _DeleteView;
|
||||
|
||||
const factory DatabaseTabBarEvent.didUpdateChildViews(
|
||||
ChildViewUpdatePB updatePB,
|
||||
) = _DidUpdateChildViews;
|
||||
|
||||
const factory DatabaseTabBarEvent.viewDidUpdate(ViewPB view) = _ViewDidUpdate;
|
||||
}
|
||||
|
||||
@@ -227,19 +247,29 @@ class DatabaseTabBarState with _$DatabaseTabBarState {
|
||||
const factory DatabaseTabBarState({
|
||||
required ViewPB parentView,
|
||||
required int selectedIndex,
|
||||
required String compactModeId,
|
||||
required bool enableCompactMode,
|
||||
required List<DatabaseTabBar> tabBars,
|
||||
required Map<String, DatabaseTabBarController> tabBarControllerByViewId,
|
||||
}) = _DatabaseTabBarState;
|
||||
|
||||
factory DatabaseTabBarState.initial(ViewPB view) {
|
||||
factory DatabaseTabBarState.initial(
|
||||
ViewPB view,
|
||||
String compactModeId,
|
||||
bool enableCompactMode,
|
||||
) {
|
||||
final tabBar = DatabaseTabBar(view: view);
|
||||
return DatabaseTabBarState(
|
||||
parentView: view,
|
||||
selectedIndex: 0,
|
||||
compactModeId: compactModeId,
|
||||
enableCompactMode: enableCompactMode,
|
||||
tabBars: [tabBar],
|
||||
tabBarControllerByViewId: {
|
||||
view.id: DatabaseTabBarController(
|
||||
view: view,
|
||||
compactModeId: compactModeId,
|
||||
enableCompactMode: enableCompactMode,
|
||||
),
|
||||
},
|
||||
);
|
||||
@@ -257,7 +287,9 @@ class DatabaseTabBar extends Equatable {
|
||||
final DatabaseTabBarItemBuilder _builder;
|
||||
|
||||
String get viewId => view.id;
|
||||
|
||||
DatabaseTabBarItemBuilder get builder => _builder;
|
||||
|
||||
ViewLayoutPB get layout => view.layout;
|
||||
|
||||
@override
|
||||
@@ -274,8 +306,18 @@ typedef OnViewChildViewChanged = void Function(
|
||||
);
|
||||
|
||||
class DatabaseTabBarController {
|
||||
DatabaseTabBarController({required this.view})
|
||||
: controller = DatabaseController(view: view),
|
||||
DatabaseTabBarController({
|
||||
required this.view,
|
||||
required String compactModeId,
|
||||
required bool enableCompactMode,
|
||||
}) : controller = DatabaseController(view: view)
|
||||
..initCompactMode(enableCompactMode)
|
||||
..addListener(
|
||||
onCompactModeChanged: (v) async {
|
||||
compactModeEventBus
|
||||
.fire(CompactModeEvent(id: compactModeId, enable: v));
|
||||
},
|
||||
),
|
||||
viewListener = ViewListener(viewId: view.id) {
|
||||
viewListener.start(
|
||||
onViewChildViewsUpdated: (update) => onViewChildViewChanged?.call(update),
|
||||
|
||||
@@ -285,6 +285,9 @@ class _BoardContentState extends State<_BoardContent> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final horizontalPadding =
|
||||
context.read<DatabasePluginWidgetBuilderSize?>()?.horizontalPadding ??
|
||||
0.0;
|
||||
return MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<BoardBloc, BoardState>(
|
||||
@@ -337,68 +340,85 @@ class _BoardContentState extends State<_BoardContent> {
|
||||
focusScope: widget.focusScope,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: AppFlowyBoard(
|
||||
boardScrollController: scrollManager,
|
||||
scrollController: scrollController,
|
||||
controller: context.read<BoardBloc>().boardController,
|
||||
groupConstraints: const BoxConstraints.tightFor(width: 256),
|
||||
config: config,
|
||||
leading: HiddenGroupsColumn(margin: config.groupHeaderPadding),
|
||||
trailing: context
|
||||
.read<BoardBloc>()
|
||||
.groupingFieldType
|
||||
?.canCreateNewGroup ??
|
||||
false
|
||||
? BoardTrailing(scrollController: scrollController)
|
||||
: const HSpace(40),
|
||||
headerBuilder: (_, groupData) => BlocProvider.value(
|
||||
value: context.read<BoardBloc>(),
|
||||
child: BoardColumnHeader(
|
||||
databaseController: databaseController,
|
||||
groupData: groupData,
|
||||
margin: config.groupHeaderPadding,
|
||||
),
|
||||
),
|
||||
footerBuilder: (_, groupData) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: context.read<BoardBloc>()),
|
||||
BlocProvider.value(value: context.read<BoardActionsCubit>()),
|
||||
],
|
||||
child: BoardColumnFooter(
|
||||
columnData: groupData,
|
||||
boardConfig: config,
|
||||
scrollManager: scrollManager,
|
||||
),
|
||||
),
|
||||
cardBuilder: (context, column, columnItem) => MultiBlocProvider(
|
||||
key: ValueKey("board_card_${column.id}_${columnItem.id}"),
|
||||
providers: [
|
||||
BlocProvider<BoardBloc>.value(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: databaseController.compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
return AppFlowyBoard(
|
||||
boardScrollController: scrollManager,
|
||||
scrollController: scrollController,
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
controller: context.read<BoardBloc>().boardController,
|
||||
groupConstraints:
|
||||
BoxConstraints.tightFor(width: compactMode ? 196 : 256),
|
||||
config: config,
|
||||
leading: HiddenGroupsColumn(
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
margin: config.groupHeaderPadding +
|
||||
EdgeInsets.only(
|
||||
left: widget.shrinkWrap ? horizontalPadding : 0.0,
|
||||
),
|
||||
),
|
||||
trailing: context
|
||||
.read<BoardBloc>()
|
||||
.groupingFieldType
|
||||
?.canCreateNewGroup ??
|
||||
false
|
||||
? BoardTrailing(scrollController: scrollController)
|
||||
: const HSpace(40),
|
||||
headerBuilder: (_, groupData) => BlocProvider.value(
|
||||
value: context.read<BoardBloc>(),
|
||||
child: BoardColumnHeader(
|
||||
databaseController: databaseController,
|
||||
groupData: groupData,
|
||||
margin: config.groupHeaderPadding,
|
||||
),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: context.read<BoardActionsCubit>(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => ViewLockStatusBloc(view: widget.view)
|
||||
..add(ViewLockStatusEvent.initial()),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<ViewLockStatusBloc, ViewLockStatusState>(
|
||||
builder: (context, state) {
|
||||
return IgnorePointer(
|
||||
ignoring: state.isLocked,
|
||||
child: _BoardCard(
|
||||
afGroupData: column,
|
||||
groupItem: columnItem as GroupItem,
|
||||
boardConfig: config,
|
||||
notifier: widget.focusScope,
|
||||
cellBuilder: cellBuilder,
|
||||
footerBuilder: (_, groupData) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: context.read<BoardBloc>()),
|
||||
BlocProvider.value(
|
||||
value: context.read<BoardActionsCubit>(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
child: BoardColumnFooter(
|
||||
columnData: groupData,
|
||||
boardConfig: config,
|
||||
scrollManager: scrollManager,
|
||||
),
|
||||
),
|
||||
cardBuilder: (context, column, columnItem) =>
|
||||
MultiBlocProvider(
|
||||
key: ValueKey("board_card_${column.id}_${columnItem.id}"),
|
||||
providers: [
|
||||
BlocProvider<BoardBloc>.value(
|
||||
value: context.read<BoardBloc>(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: context.read<BoardActionsCubit>(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (_) => ViewLockStatusBloc(view: widget.view)
|
||||
..add(ViewLockStatusEvent.initial()),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<ViewLockStatusBloc, ViewLockStatusState>(
|
||||
builder: (context, state) {
|
||||
return IgnorePointer(
|
||||
ignoring: state.isLocked,
|
||||
child: _BoardCard(
|
||||
afGroupData: column,
|
||||
groupItem: columnItem as GroupItem,
|
||||
boardConfig: config,
|
||||
notifier: widget.focusScope,
|
||||
cellBuilder: cellBuilder,
|
||||
compactMode: compactMode,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -560,6 +580,7 @@ class _BoardCard extends StatefulWidget {
|
||||
required this.boardConfig,
|
||||
required this.cellBuilder,
|
||||
required this.notifier,
|
||||
required this.compactMode,
|
||||
});
|
||||
|
||||
final AppFlowyGroupData afGroupData;
|
||||
@@ -567,6 +588,7 @@ class _BoardCard extends StatefulWidget {
|
||||
final AppFlowyBoardConfig boardConfig;
|
||||
final CardCellBuilder cellBuilder;
|
||||
final BoardFocusScope notifier;
|
||||
final bool compactMode;
|
||||
|
||||
@override
|
||||
State<_BoardCard> createState() => _BoardCardState();
|
||||
@@ -653,15 +675,21 @@ class _BoardCardState extends State<_BoardCard> {
|
||||
|
||||
return previousContainsFocus != currentContainsFocus;
|
||||
},
|
||||
builder: (context, focusedItems, child) => Container(
|
||||
margin: widget.boardConfig.cardMargin,
|
||||
decoration: _makeBoxDecoration(
|
||||
context,
|
||||
groupData.group.groupId,
|
||||
widget.groupItem.id,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
builder: (context, focusedItems, child) {
|
||||
final cardMargin = widget.boardConfig.cardMargin;
|
||||
final margin = widget.compactMode
|
||||
? cardMargin - EdgeInsets.symmetric(horizontal: 2)
|
||||
: cardMargin;
|
||||
return Container(
|
||||
margin: margin,
|
||||
decoration: _makeBoxDecoration(
|
||||
context,
|
||||
groupData.group.groupId,
|
||||
widget.groupItem.id,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: RowCard(
|
||||
fieldController: databaseController.fieldController,
|
||||
rowMeta: rowMeta,
|
||||
|
||||
+9
@@ -2,10 +2,13 @@ import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/view_database_button.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class BoardSettingBar extends StatelessWidget {
|
||||
const BoardSettingBar({
|
||||
@@ -30,6 +33,8 @@ class BoardSettingBar extends StatelessWidget {
|
||||
if (value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final isReference =
|
||||
Provider.of<ReferenceState?>(context)?.isReference ?? false;
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
@@ -38,6 +43,10 @@ class BoardSettingBar extends StatelessWidget {
|
||||
FilterButton(
|
||||
toggleExtension: toggleExtension,
|
||||
),
|
||||
if (isReference) ...[
|
||||
const HSpace(2),
|
||||
ViewDatabaseButton(view: databaseController.view),
|
||||
],
|
||||
const HSpace(2),
|
||||
SettingButton(
|
||||
databaseController: databaseController,
|
||||
|
||||
+16
-8
@@ -1,8 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
@@ -16,20 +13,24 @@ import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class HiddenGroupsColumn extends StatelessWidget {
|
||||
const HiddenGroupsColumn({
|
||||
super.key,
|
||||
required this.margin,
|
||||
required this.shrinkWrap,
|
||||
});
|
||||
|
||||
final EdgeInsets margin;
|
||||
final bool shrinkWrap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -85,11 +86,7 @@ class HiddenGroupsColumn extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: HiddenGroupList(
|
||||
databaseController: databaseController,
|
||||
),
|
||||
),
|
||||
_hiddenGroupList(databaseController),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -98,6 +95,14 @@ class HiddenGroupsColumn extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _hiddenGroupList(DatabaseController databaseController) {
|
||||
final hiddenGroupList = HiddenGroupList(
|
||||
shrinkWrap: shrinkWrap,
|
||||
databaseController: databaseController,
|
||||
);
|
||||
return shrinkWrap ? hiddenGroupList : Expanded(child: hiddenGroupList);
|
||||
}
|
||||
|
||||
Widget _collapseExpandIcon(BuildContext context, bool isCollapsed) {
|
||||
return FlowyTooltip(
|
||||
message: isCollapsed
|
||||
@@ -125,9 +130,11 @@ class HiddenGroupList extends StatelessWidget {
|
||||
const HiddenGroupList({
|
||||
super.key,
|
||||
required this.databaseController,
|
||||
required this.shrinkWrap,
|
||||
});
|
||||
|
||||
final DatabaseController databaseController;
|
||||
final bool shrinkWrap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -150,6 +157,7 @@ class HiddenGroupList extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
shrinkWrap: shrinkWrap,
|
||||
buildDefaultDragHandles: false,
|
||||
itemCount: state.hiddenGroups.length,
|
||||
itemBuilder: (_, index) => Padding(
|
||||
|
||||
+1
@@ -289,6 +289,7 @@ class _TitleTextCellSkin extends IEditableTextCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TextCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
|
||||
+9
@@ -2,10 +2,13 @@ import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/view_database_button.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CalendarSettingBar extends StatelessWidget {
|
||||
const CalendarSettingBar({
|
||||
@@ -30,6 +33,8 @@ class CalendarSettingBar extends StatelessWidget {
|
||||
if (value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final isReference =
|
||||
Provider.of<ReferenceState?>(context)?.isReference ?? false;
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
@@ -38,6 +43,10 @@ class CalendarSettingBar extends StatelessWidget {
|
||||
FilterButton(
|
||||
toggleExtension: toggleExtension,
|
||||
),
|
||||
if (isReference) ...[
|
||||
const HSpace(2),
|
||||
ViewDatabaseButton(view: databaseController.view),
|
||||
],
|
||||
const HSpace(2),
|
||||
SettingButton(
|
||||
databaseController: databaseController,
|
||||
|
||||
@@ -298,6 +298,7 @@ class _GridPageContentState extends State<GridPageContent> {
|
||||
_GridHeader(
|
||||
headerScrollController: headerScrollController,
|
||||
editable: !context.read<ViewLockStatusBloc>().state.isLocked,
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
),
|
||||
_GridRows(
|
||||
viewId: widget.view.id,
|
||||
@@ -313,10 +314,12 @@ class _GridHeader extends StatelessWidget {
|
||||
const _GridHeader({
|
||||
required this.headerScrollController,
|
||||
required this.editable,
|
||||
required this.shrinkWrap,
|
||||
});
|
||||
|
||||
final ScrollController headerScrollController;
|
||||
final bool editable;
|
||||
final bool shrinkWrap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -324,6 +327,7 @@ class _GridHeader extends StatelessWidget {
|
||||
builder: (_, state) => GridHeaderSliverAdaptor(
|
||||
viewId: state.viewId,
|
||||
anchorScrollController: headerScrollController,
|
||||
shrinkWrap: shrinkWrap,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -416,12 +420,13 @@ class _GridRowsState extends State<_GridRows> {
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: GridLayout.headerWidth(
|
||||
context
|
||||
.read<DatabasePluginWidgetBuilderSize>()
|
||||
.horizontalPadding,
|
||||
.read<DatabasePluginWidgetBuilderSize>()
|
||||
.horizontalPadding *
|
||||
3,
|
||||
context.read<GridBloc>().state.fields,
|
||||
),
|
||||
),
|
||||
child: _renderList(context),
|
||||
child: _shrinkWrapRenderList(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -452,9 +457,31 @@ class _GridRowsState extends State<_GridRows> {
|
||||
return Flexible(child: child);
|
||||
}
|
||||
|
||||
Widget _shrinkWrapRenderList(BuildContext context) {
|
||||
final state = context.read<GridBloc>().state;
|
||||
final horizontalPadding =
|
||||
context.read<DatabasePluginWidgetBuilderSize?>()?.horizontalPadding ??
|
||||
0.0;
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
|
||||
children: [
|
||||
widget.shrinkWrap
|
||||
? _reorderableListView(state)
|
||||
: Expanded(child: _reorderableListView(state)),
|
||||
if (showFloatingCalculations && !widget.shrinkWrap) ...[
|
||||
_PositionedCalculationsRow(
|
||||
viewId: widget.viewId,
|
||||
isAtBottom: isAtBottom,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderList(BuildContext context) {
|
||||
final state = context.read<GridBloc>().state;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||
|
||||
import 'sizes.dart';
|
||||
|
||||
class GridLayout {
|
||||
|
||||
@@ -5,16 +5,26 @@ class GridSize {
|
||||
static double scale = 1;
|
||||
|
||||
static double get scrollBarSize => 8 * scale;
|
||||
|
||||
static double get headerHeight => 36 * scale;
|
||||
|
||||
static double get buttonHeight => 38 * scale;
|
||||
|
||||
static double get footerHeight => 36 * scale;
|
||||
|
||||
static double get horizontalHeaderPadding =>
|
||||
UniversalPlatform.isDesktop ? 40 * scale : 16 * scale;
|
||||
|
||||
static double get cellHPadding => 10 * scale;
|
||||
static double get cellVPadding => 10 * scale;
|
||||
|
||||
static double get cellVPadding => 8 * scale;
|
||||
|
||||
static double get popoverItemHeight => 26 * scale;
|
||||
|
||||
static double get typeOptionSeparatorHeight => 4 * scale;
|
||||
|
||||
static double get newPropertyButtonWidth => 140 * scale;
|
||||
|
||||
static double get mobileNewPropertyButtonWidth => 200 * scale;
|
||||
|
||||
static EdgeInsets get cellContentInsets => EdgeInsets.symmetric(
|
||||
@@ -22,10 +32,8 @@ class GridSize {
|
||||
vertical: GridSize.cellVPadding,
|
||||
);
|
||||
|
||||
static EdgeInsets get fieldContentInsets => EdgeInsets.symmetric(
|
||||
horizontal: GridSize.cellHPadding,
|
||||
vertical: GridSize.cellVPadding,
|
||||
);
|
||||
static EdgeInsets get compactCellContentInsets =>
|
||||
cellContentInsets - EdgeInsets.symmetric(vertical: 2);
|
||||
|
||||
static EdgeInsets get typeOptionContentInsets => const EdgeInsets.all(4);
|
||||
|
||||
|
||||
+13
-3
@@ -21,11 +21,13 @@ class GridHeaderSliverAdaptor extends StatefulWidget {
|
||||
const GridHeaderSliverAdaptor({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.shrinkWrap,
|
||||
required this.anchorScrollController,
|
||||
});
|
||||
|
||||
final String viewId;
|
||||
final ScrollController anchorScrollController;
|
||||
final bool shrinkWrap;
|
||||
|
||||
@override
|
||||
State<GridHeaderSliverAdaptor> createState() =>
|
||||
@@ -37,6 +39,9 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||
Widget build(BuildContext context) {
|
||||
final fieldController =
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
final horizontalPadding =
|
||||
context.read<DatabasePluginWidgetBuilderSize?>()?.horizontalPadding ??
|
||||
0.0;
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return GridHeaderBloc(
|
||||
@@ -47,9 +52,14 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: widget.anchorScrollController,
|
||||
child: _GridHeader(
|
||||
viewId: widget.viewId,
|
||||
fieldController: fieldController,
|
||||
child: Padding(
|
||||
padding: widget.shrinkWrap
|
||||
? EdgeInsets.symmetric(horizontal: horizontalPadding)
|
||||
: EdgeInsets.zero,
|
||||
child: _GridHeader(
|
||||
viewId: widget.viewId,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+14
-8
@@ -308,14 +308,20 @@ class RowContent extends StatelessWidget {
|
||||
Widget _finalCellDecoration(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.basic,
|
||||
child: Container(
|
||||
width: GridSize.newPropertyButtonWidth,
|
||||
constraints: const BoxConstraints(minHeight: 36),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(color: AFThemeExtension.of(context).borderColor),
|
||||
),
|
||||
),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: cellBuilder.databaseController.compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
return Container(
|
||||
width: GridSize.newPropertyButtonWidth,
|
||||
constraints: BoxConstraints(minHeight: compactMode ? 32 : 36),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom:
|
||||
BorderSide(color: AFThemeExtension.of(context).borderColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+19
-22
@@ -1,14 +1,13 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../layout/sizes.dart';
|
||||
import '../filter/create_filter_list.dart';
|
||||
|
||||
class FilterButton extends StatefulWidget {
|
||||
@@ -30,27 +29,25 @@ class _FilterButtonState extends State<FilterButton> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<FilterEditorBloc, FilterEditorState>(
|
||||
builder: (context, state) {
|
||||
final textColor = state.filters.isEmpty
|
||||
? Theme.of(context).hintColor
|
||||
: Theme.of(context).colorScheme.primary;
|
||||
|
||||
return _wrapPopover(
|
||||
FlowyTextButton(
|
||||
LocaleKeys.grid_settings_filter.tr(),
|
||||
fontColor: textColor,
|
||||
fontSize: FontSizes.s12,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.toolbarSettingButtonInsets,
|
||||
radius: Corners.s4Border,
|
||||
onPressed: () {
|
||||
final bloc = context.read<FilterEditorBloc>();
|
||||
if (bloc.state.filters.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
widget.toggleExtension.toggle();
|
||||
}
|
||||
},
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowyIconButton(
|
||||
tooltipText: LocaleKeys.grid_settings_filter.tr(),
|
||||
width: 24,
|
||||
height: 24,
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
icon: const FlowySvg(FlowySvgs.database_filter_s),
|
||||
onPressed: () {
|
||||
final bloc = context.read<FilterEditorBloc>();
|
||||
if (bloc.state.filters.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
widget.toggleExtension.toggle();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+12
-5
@@ -3,12 +3,15 @@ import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_
|
||||
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'filter_button.dart';
|
||||
import 'sort_button.dart';
|
||||
import 'view_database_button.dart';
|
||||
|
||||
class GridSettingBar extends StatelessWidget {
|
||||
const GridSettingBar({
|
||||
@@ -43,18 +46,22 @@ class GridSettingBar extends StatelessWidget {
|
||||
if (isLoading) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final isReference =
|
||||
Provider.of<ReferenceState?>(context)?.isReference ?? false;
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FilterButton(toggleExtension: toggleExtension),
|
||||
const HSpace(6),
|
||||
const HSpace(2),
|
||||
SortButton(toggleExtension: toggleExtension),
|
||||
const HSpace(6),
|
||||
SettingButton(
|
||||
databaseController: controller,
|
||||
),
|
||||
if (isReference) ...[
|
||||
const HSpace(2),
|
||||
ViewDatabaseButton(view: controller.view),
|
||||
],
|
||||
const HSpace(2),
|
||||
SettingButton(databaseController: controller),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
+18
-21
@@ -1,13 +1,12 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
|
||||
import '../sort/create_sort_list.dart';
|
||||
|
||||
@@ -27,26 +26,24 @@ class _SortButtonState extends State<SortButton> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SortEditorBloc, SortEditorState>(
|
||||
builder: (context, state) {
|
||||
final textColor = state.sorts.isEmpty
|
||||
? Theme.of(context).hintColor
|
||||
: Theme.of(context).colorScheme.primary;
|
||||
|
||||
return wrapPopover(
|
||||
FlowyTextButton(
|
||||
LocaleKeys.grid_settings_sort.tr(),
|
||||
fontColor: textColor,
|
||||
fontSize: FontSizes.s12,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.toolbarSettingButtonInsets,
|
||||
radius: Corners.s4Border,
|
||||
onPressed: () {
|
||||
if (state.sorts.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
widget.toggleExtension.toggle();
|
||||
}
|
||||
},
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowyIconButton(
|
||||
tooltipText: LocaleKeys.grid_settings_sort.tr(),
|
||||
width: 24,
|
||||
height: 24,
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
icon: const FlowySvg(FlowySvgs.database_sort_s),
|
||||
onPressed: () {
|
||||
if (state.sorts.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
widget.toggleExtension.toggle();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ViewDatabaseButton extends StatelessWidget {
|
||||
const ViewDatabaseButton({super.key, required this.view});
|
||||
|
||||
final ViewPB view;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowyIconButton(
|
||||
tooltipText: LocaleKeys.grid_rowPage_openAsFullPage.tr(),
|
||||
width: 24,
|
||||
height: 24,
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
icon: const FlowySvg(FlowySvgs.database_fullscreen_s),
|
||||
onPressed: () {
|
||||
getIt<TabsBloc>().add(
|
||||
TabsEvent.openPlugin(
|
||||
plugin: view.plugin(),
|
||||
view: view,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/tab.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
@@ -16,6 +16,7 @@ import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'tab_bar_add_button.dart';
|
||||
|
||||
@@ -26,12 +27,8 @@ class TabBarHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
return SizedBox(
|
||||
height: 35,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal:
|
||||
context.read<DatabasePluginWidgetBuilderSize>().horizontalPadding,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
@@ -332,7 +329,24 @@ class _TabBarItemButtonState extends State<TabBarItemButton> {
|
||||
enableColor: false,
|
||||
);
|
||||
}
|
||||
return Opacity(opacity: 0.6, child: icon);
|
||||
final isReference =
|
||||
Provider.of<ReferenceState?>(context)?.isReference ?? false;
|
||||
final iconWidget = Opacity(opacity: 0.6, child: icon);
|
||||
return isReference
|
||||
? Stack(
|
||||
children: [
|
||||
iconWidget,
|
||||
const Positioned(
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.referenced_page_s,
|
||||
blendMode: BlendMode.dstIn,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: iconWidget;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/compact_mode_event.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart';
|
||||
import 'package:appflowy/plugins/shared/share/share_button.dart';
|
||||
import 'package:appflowy/plugins/util.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
|
||||
@@ -12,7 +20,9 @@ import 'package:appflowy/workspace/presentation/widgets/favorite_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -55,31 +65,84 @@ abstract class DatabaseTabBarItemBuilder {
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class DatabaseTabBarView extends StatelessWidget {
|
||||
class DatabaseTabBarView extends StatefulWidget {
|
||||
const DatabaseTabBarView({
|
||||
super.key,
|
||||
required this.view,
|
||||
required this.shrinkWrap,
|
||||
required this.showActions,
|
||||
this.initialRowId,
|
||||
this.actionBuilder,
|
||||
this.node,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final bool shrinkWrap;
|
||||
final BlockComponentActionBuilder? actionBuilder;
|
||||
final bool showActions;
|
||||
final Node? node;
|
||||
|
||||
/// Used to open a Row on plugin load
|
||||
///
|
||||
final String? initialRowId;
|
||||
|
||||
@override
|
||||
State<DatabaseTabBarView> createState() => _DatabaseTabBarViewState();
|
||||
}
|
||||
|
||||
class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
||||
bool enableCompactMode = false;
|
||||
bool initialed = false;
|
||||
StreamSubscription<CompactModeEvent>? compactModeSubscription;
|
||||
|
||||
String get compactModeId => widget.node?.id ?? widget.view.id;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.node != null) {
|
||||
enableCompactMode =
|
||||
widget.node!.attributes[DatabaseBlockKeys.enableCompactMode] ?? false;
|
||||
setState(() {
|
||||
initialed = true;
|
||||
});
|
||||
} else {
|
||||
fetchLocalCompactMode(compactModeId).then((v) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
enableCompactMode = v;
|
||||
initialed = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
compactModeSubscription =
|
||||
compactModeEventBus.on<CompactModeEvent>().listen((event) {
|
||||
if (event.id != widget.view.id) return;
|
||||
updateLocalCompactMode(event.enable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
compactModeSubscription?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!initialed) return Center(child: CircularProgressIndicator());
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<DatabaseTabBarBloc>(
|
||||
create: (_) => DatabaseTabBarBloc(view: view)
|
||||
..add(const DatabaseTabBarEvent.initial()),
|
||||
create: (_) => DatabaseTabBarBloc(
|
||||
view: widget.view,
|
||||
compactModeId: compactModeId,
|
||||
enableCompactMode: enableCompactMode,
|
||||
)..add(const DatabaseTabBarEvent.initial()),
|
||||
),
|
||||
BlocProvider<ViewBloc>(
|
||||
create: (_) => ViewBloc(view: view)
|
||||
create: (_) => ViewBloc(view: widget.view)
|
||||
..add(
|
||||
const ViewEvent.initial(),
|
||||
),
|
||||
@@ -88,9 +151,15 @@ class DatabaseTabBarView extends StatelessWidget {
|
||||
child: BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
|
||||
builder: (innerContext, state) {
|
||||
final layout = state.tabBars[state.selectedIndex].layout;
|
||||
|
||||
final isCalendar = layout == ViewLayoutPB.Calendar;
|
||||
final horizontalPadding =
|
||||
context.read<DatabasePluginWidgetBuilderSize>().horizontalPadding;
|
||||
final showActionWrapper = widget.showActions &&
|
||||
widget.actionBuilder != null &&
|
||||
widget.node != null;
|
||||
final Widget child = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (UniversalPlatform.isMobile) const VSpace(12),
|
||||
ValueListenableBuilder<bool>(
|
||||
@@ -113,13 +182,39 @@ class DatabaseTabBarView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
if (showActionWrapper) {
|
||||
child = BlockComponentActionWrapper(
|
||||
node: widget.node!,
|
||||
actionBuilder: widget.actionBuilder!,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: horizontalPadding),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (UniversalPlatform.isDesktop) {
|
||||
child = Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: horizontalPadding,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
},
|
||||
),
|
||||
pageSettingBarExtensionFromState(context, state),
|
||||
wrapContent(
|
||||
layout: layout,
|
||||
child: pageContentFromState(context, state),
|
||||
child: Padding(
|
||||
padding:
|
||||
(isCalendar && widget.shrinkWrap || showActionWrapper)
|
||||
? EdgeInsets.only(left: 42 - horizontalPadding)
|
||||
: EdgeInsets.zero,
|
||||
child: pageContentFromState(context, state),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -130,8 +225,44 @@ class DatabaseTabBarView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> fetchLocalCompactMode(String compactModeId) async {
|
||||
Set<String> compactModeIds = {};
|
||||
try {
|
||||
final localIds = await getIt<KeyValueStorage>().get(
|
||||
KVKeys.compactModeIds,
|
||||
);
|
||||
final List<dynamic> decodedList = jsonDecode(localIds ?? '');
|
||||
compactModeIds = Set.from(decodedList.map((item) => item as String));
|
||||
} catch (e) {
|
||||
Log.warn('fetch local compact mode from id :$compactModeId failed', e);
|
||||
}
|
||||
return compactModeIds.contains(compactModeId);
|
||||
}
|
||||
|
||||
Future<void> updateLocalCompactMode(bool enableCompactMode) async {
|
||||
Set<String> compactModeIds = {};
|
||||
try {
|
||||
final localIds = await getIt<KeyValueStorage>().get(
|
||||
KVKeys.compactModeIds,
|
||||
);
|
||||
final List<dynamic> decodedList = jsonDecode(localIds ?? '');
|
||||
compactModeIds = Set.from(decodedList.map((item) => item as String));
|
||||
} catch (e) {
|
||||
Log.warn('get compact mode ids failed', e);
|
||||
}
|
||||
if (enableCompactMode) {
|
||||
compactModeIds.add(compactModeId);
|
||||
} else {
|
||||
compactModeIds.remove(compactModeId);
|
||||
}
|
||||
await getIt<KeyValueStorage>().set(
|
||||
KVKeys.compactModeIds,
|
||||
jsonEncode(compactModeIds.toList()),
|
||||
);
|
||||
}
|
||||
|
||||
Widget wrapContent({required ViewLayoutPB layout, required Widget child}) {
|
||||
if (shrinkWrap) {
|
||||
if (widget.shrinkWrap) {
|
||||
if (layout.shrinkWrappable) {
|
||||
return child;
|
||||
}
|
||||
@@ -153,8 +284,8 @@ class DatabaseTabBarView extends StatelessWidget {
|
||||
context,
|
||||
tab.view,
|
||||
controller,
|
||||
shrinkWrap,
|
||||
initialRowId,
|
||||
widget.shrinkWrap,
|
||||
widget.initialRowId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -226,6 +357,9 @@ class DatabaseTabBarViewPlugin extends Plugin {
|
||||
}
|
||||
|
||||
const kDatabasePluginWidgetBuilderHorizontalPadding = 'horizontal_padding';
|
||||
const kDatabasePluginWidgetBuilderShowActions = 'show_actions';
|
||||
const kDatabasePluginWidgetBuilderActionBuilder = 'action_builder';
|
||||
const kDatabasePluginWidgetBuilderNode = 'node';
|
||||
|
||||
class DatabasePluginWidgetBuilderSize {
|
||||
const DatabasePluginWidgetBuilderSize({required this.horizontalPadding});
|
||||
@@ -274,6 +408,11 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
|
||||
final horizontalPadding =
|
||||
data?[kDatabasePluginWidgetBuilderHorizontalPadding] as double? ??
|
||||
GridSize.horizontalHeaderPadding + 40;
|
||||
final BlockComponentActionBuilder? actionBuilder =
|
||||
data?[kDatabasePluginWidgetBuilderActionBuilder];
|
||||
final bool showActions =
|
||||
data?[kDatabasePluginWidgetBuilderShowActions] ?? false;
|
||||
final Node? node = data?[kDatabasePluginWidgetBuilderNode];
|
||||
|
||||
return Provider(
|
||||
create: (context) => DatabasePluginWidgetBuilderSize(
|
||||
@@ -284,6 +423,9 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
|
||||
view: notifier.view,
|
||||
shrinkWrap: shrinkWrap,
|
||||
initialRowId: initialRowId,
|
||||
actionBuilder: actionBuilder,
|
||||
showActions: showActions,
|
||||
node: node,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card.dart';
|
||||
@@ -16,12 +14,12 @@ import 'package:collection/collection.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../cell/card_cell_builder.dart';
|
||||
import '../cell/card_cell_skeleton/card_cell.dart';
|
||||
|
||||
import 'card_bloc.dart';
|
||||
import 'container/accessory.dart';
|
||||
import 'container/card_container.dart';
|
||||
@@ -462,7 +460,7 @@ class RowCardStyleConfiguration {
|
||||
const RowCardStyleConfiguration({
|
||||
required this.cellStyleMap,
|
||||
this.showAccessory = true,
|
||||
this.cardPadding = const EdgeInsets.all(8),
|
||||
this.cardPadding = const EdgeInsets.all(4),
|
||||
this.hoverStyle,
|
||||
});
|
||||
|
||||
|
||||
+3
-3
@@ -40,7 +40,7 @@ class RowCardContainer extends StatelessWidget {
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: 42),
|
||||
constraints: const BoxConstraints(minHeight: 36),
|
||||
child: _CardEnterRegion(
|
||||
shouldBuildAccessory: shouldBuildAccessory,
|
||||
accessories: accessories,
|
||||
@@ -77,8 +77,8 @@ class _CardEnterRegion extends StatelessWidget {
|
||||
child,
|
||||
if (onEnter && shouldBuildAccessory)
|
||||
Positioned(
|
||||
top: 10.0,
|
||||
right: 10.0,
|
||||
top: 7.0,
|
||||
right: 7.0,
|
||||
child: CardAccessoryContainer(
|
||||
accessories: accessories,
|
||||
onTapAccessory: onTapAccessory,
|
||||
|
||||
+23
-14
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -12,22 +12,31 @@ class DesktopGridCheckboxCellSkin extends IEditableCheckboxCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
CheckboxCellBloc bloc,
|
||||
CheckboxCellState state,
|
||||
) {
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
onPressed: () => bloc.add(const CheckboxCellEvent.select()),
|
||||
icon: FlowySvg(
|
||||
state.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
size: const Size.square(20),
|
||||
),
|
||||
width: 20,
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: padding,
|
||||
child: FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
onPressed: () => bloc.add(const CheckboxCellEvent.select()),
|
||||
icon: FlowySvg(
|
||||
state.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
size: const Size.square(20),
|
||||
),
|
||||
width: 20,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+20
-11
@@ -1,11 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../editable_cell_skeleton/checklist.dart';
|
||||
@@ -15,6 +14,7 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
ChecklistCellBloc bloc,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
@@ -39,15 +39,24 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin {
|
||||
onClose: () => cellContainerNotifier.isFocus = false,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: state.tasks.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: ChecklistProgressBar(
|
||||
tasks: state.tasks,
|
||||
percent: state.percent,
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: padding,
|
||||
child: state.tasks.isEmpty
|
||||
? const SizedBox.shrink()
|
||||
: ChecklistProgressBar(
|
||||
tasks: state.tasks,
|
||||
percent: state.percent,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
+37
-25
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/date_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -15,6 +15,7 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
DateCellBloc bloc,
|
||||
DateCellState state,
|
||||
PopoverController popoverController,
|
||||
@@ -28,11 +29,11 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin {
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: state.fieldInfo.wrapCellContent ?? false
|
||||
? _buildCellContent(state)
|
||||
? _buildCellContent(state, compactModeNotifier)
|
||||
: SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: _buildCellContent(state),
|
||||
child: _buildCellContent(state, compactModeNotifier),
|
||||
),
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContent) {
|
||||
@@ -47,33 +48,44 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCellContent(DateCellState state) {
|
||||
Widget _buildCellContent(
|
||||
DateCellState state,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
) {
|
||||
final wrap = state.fieldInfo.wrapCellContent ?? false;
|
||||
final dateStr = getDateCellStrFromCellData(
|
||||
state.fieldInfo,
|
||||
state.cellData,
|
||||
);
|
||||
return Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText(
|
||||
dateStr,
|
||||
overflow: wrap ? null : TextOverflow.ellipsis,
|
||||
maxLines: wrap ? null : 1,
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText(
|
||||
dateStr,
|
||||
overflow: wrap ? null : TextOverflow.ellipsis,
|
||||
maxLines: wrap ? null : 1,
|
||||
),
|
||||
),
|
||||
if (state.cellData.reminderId.isNotEmpty) ...[
|
||||
const HSpace(4),
|
||||
FlowyTooltip(
|
||||
message: LocaleKeys.grid_field_reminderOnDateTooltip.tr(),
|
||||
child: const FlowySvg(FlowySvgs.clock_alarm_s),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (state.cellData.reminderId.isNotEmpty) ...[
|
||||
const HSpace(4),
|
||||
FlowyTooltip(
|
||||
message: LocaleKeys.grid_field_reminderOnDateTooltip.tr(),
|
||||
child: const FlowySvg(FlowySvgs.clock_alarm_s),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -72,7 +72,7 @@ class GridMediaCellSkin extends IEditableMediaCellSkin {
|
||||
|
||||
if (!isMobile && wrapContent) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Wrap(
|
||||
@@ -233,7 +233,7 @@ class _FilePreviewRender extends StatelessWidget {
|
||||
height: 28,
|
||||
width: 28,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
padding: const EdgeInsets.all(8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: AFThemeExtension.of(context).greyHover,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
|
||||
+28
-18
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -11,27 +11,37 @@ class DesktopGridNumberCellSkin extends IEditableNumberCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
NumberCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
) {
|
||||
return TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: context.watch<NumberCellBloc>().state.wrap ? null : 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: GridSize.cellContentInsets,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
|
||||
return TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: context.watch<NumberCellBloc>().state.wrap ? null : 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: padding,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+24
-9
@@ -1,8 +1,9 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -16,10 +17,12 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
final userWorkspaceBloc = context.read<UserWorkspaceBloc>();
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
@@ -27,16 +30,24 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
||||
margin: EdgeInsets.zero,
|
||||
onClose: () => cellContainerNotifier.isFocus = false,
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: userWorkspaceBloc),
|
||||
BlocProvider.value(value: bloc),
|
||||
],
|
||||
child: const RelationCellEditor(),
|
||||
);
|
||||
},
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: state.wrap
|
||||
? _buildWrapRows(context, state.rows)
|
||||
: _buildNoWrapRows(context, state.rows),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
return state.wrap
|
||||
? _buildWrapRows(context, state.rows, compactMode)
|
||||
: _buildNoWrapRows(context, state.rows, compactMode);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -44,9 +55,12 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
||||
Widget _buildWrapRows(
|
||||
BuildContext context,
|
||||
List<RelatedRowDataPB> rows,
|
||||
bool compactMode,
|
||||
) {
|
||||
return Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
padding: compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets,
|
||||
child: Wrap(
|
||||
runSpacing: 4,
|
||||
spacing: 4.0,
|
||||
@@ -68,6 +82,7 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin {
|
||||
Widget _buildNoWrapRows(
|
||||
BuildContext context,
|
||||
List<RelatedRowDataPB> rows,
|
||||
bool compactMode,
|
||||
) {
|
||||
return SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
|
||||
+71
-41
@@ -15,6 +15,7 @@ class DesktopGridSelectOptionCellSkin extends IEditableSelectOptionCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
SelectOptionCellBloc bloc,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
@@ -35,63 +36,92 @@ class DesktopGridSelectOptionCellSkin extends IEditableSelectOptionCellSkin {
|
||||
return Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: state.wrap
|
||||
? _buildWrapOptions(context, state.selectedOptions)
|
||||
: _buildNoWrapOptions(context, state.selectedOptions),
|
||||
? _buildWrapOptions(
|
||||
context,
|
||||
state.selectedOptions,
|
||||
compactModeNotifier,
|
||||
)
|
||||
: _buildNoWrapOptions(
|
||||
context,
|
||||
state.selectedOptions,
|
||||
compactModeNotifier,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWrapOptions(BuildContext context, List<SelectOptionPB> options) {
|
||||
return Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: Wrap(
|
||||
runSpacing: 4,
|
||||
children: options.map(
|
||||
(option) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: SelectOptionTag(
|
||||
option: option,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
Widget _buildWrapOptions(
|
||||
BuildContext context,
|
||||
List<SelectOptionPB> options,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Wrap(
|
||||
runSpacing: 4,
|
||||
children: options.map(
|
||||
(option) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: SelectOptionTag(
|
||||
option: option,
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: compactMode ? 2 : 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNoWrapOptions(
|
||||
BuildContext context,
|
||||
List<SelectOptionPB> options,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
) {
|
||||
return SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: options.map(
|
||||
(option) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: SelectOptionTag(
|
||||
option: option,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 1,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: options.map(
|
||||
(option) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: SelectOptionTag(
|
||||
option: option,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 1,
|
||||
horizontal: 8,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+63
-51
@@ -11,6 +11,7 @@ class DesktopGridSummaryCellSkin extends IEditableSummaryCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
SummaryCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -27,58 +28,69 @@ class DesktopGridSummaryCellSkin extends IEditableSummaryCellSkin {
|
||||
onExit: (p) =>
|
||||
Provider.of<SummaryMouseNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: GridSize.headerHeight,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
enabled: false,
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: GridSize.cellContentInsets,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: compactMode
|
||||
? GridSize.headerHeight - 4
|
||||
: GridSize.headerHeight,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.cellVPadding,
|
||||
),
|
||||
child: Consumer<SummaryMouseNotifier>(
|
||||
builder: (
|
||||
BuildContext context,
|
||||
SummaryMouseNotifier notifier,
|
||||
Widget? child,
|
||||
) {
|
||||
if (notifier.onEnter) {
|
||||
return SummaryCellAccessory(
|
||||
viewId: bloc.cellController.viewId,
|
||||
fieldId: bloc.cellController.fieldId,
|
||||
rowId: bloc.cellController.rowId,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
).positioned(right: 0, bottom: 8),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
enabled: false,
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: padding,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.cellVPadding,
|
||||
),
|
||||
child: Consumer<SummaryMouseNotifier>(
|
||||
builder: (
|
||||
BuildContext context,
|
||||
SummaryMouseNotifier notifier,
|
||||
Widget? child,
|
||||
) {
|
||||
if (notifier.onEnter) {
|
||||
return SummaryCellAccessory(
|
||||
viewId: bloc.cellController.viewId,
|
||||
fieldId: bloc.cellController.fieldId,
|
||||
rowId: bloc.cellController.rowId,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
).positioned(right: 0, bottom: compactMode ? 4 : 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+39
-30
@@ -13,43 +13,52 @@ class DesktopGridTextCellSkin extends IEditableTextCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TextCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
) {
|
||||
return Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const _IconOrEmoji(),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
maxLines: context.watch<TextCellBloc>().state.wrap ? null : 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: context
|
||||
.read<TextCellBloc>()
|
||||
.cellController
|
||||
.fieldInfo
|
||||
.isPrimary
|
||||
? FontWeight.w500
|
||||
: null,
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, data) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const _IconOrEmoji(),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
maxLines: context.watch<TextCellBloc>().state.wrap ? null : 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: context
|
||||
.read<TextCellBloc>()
|
||||
.cellController
|
||||
.fieldInfo
|
||||
.isPrimary
|
||||
? FontWeight.w500
|
||||
: null,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
isCollapsed: true,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
isCollapsed: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-11
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@@ -11,29 +11,41 @@ class DesktopGridTimestampCellSkin extends IEditableTimestampCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TimestampCellBloc bloc,
|
||||
TimestampCellState state,
|
||||
) {
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: state.wrap
|
||||
? _buildCellContent(state)
|
||||
? _buildCellContent(state, compactModeNotifier)
|
||||
: SingleChildScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: _buildCellContent(state),
|
||||
child: _buildCellContent(state, compactModeNotifier),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCellContent(TimestampCellState state) {
|
||||
return Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: FlowyText(
|
||||
state.dateStr,
|
||||
overflow: state.wrap ? null : TextOverflow.ellipsis,
|
||||
maxLines: state.wrap ? null : 1,
|
||||
),
|
||||
Widget _buildCellContent(
|
||||
TimestampCellState state,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: FlowyText(
|
||||
state.dateStr,
|
||||
overflow: state.wrap ? null : TextOverflow.ellipsis,
|
||||
maxLines: state.wrap ? null : 1,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+73
-61
@@ -11,6 +11,7 @@ class DesktopGridTranslateCellSkin extends IEditableTranslateCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TranslateCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -18,68 +19,79 @@ class DesktopGridTranslateCellSkin extends IEditableTranslateCellSkin {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => TranslateMouseNotifier(),
|
||||
builder: (context, child) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
opaque: false,
|
||||
onEnter: (p) =>
|
||||
Provider.of<TranslateMouseNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<TranslateMouseNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: GridSize.headerHeight,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
readOnly: true,
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: GridSize.cellContentInsets,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
opaque: false,
|
||||
onEnter: (p) =>
|
||||
Provider.of<TranslateMouseNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<TranslateMouseNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: compactMode
|
||||
? GridSize.headerHeight - 4
|
||||
: GridSize.headerHeight,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.cellVPadding,
|
||||
),
|
||||
child: Consumer<TranslateMouseNotifier>(
|
||||
builder: (
|
||||
BuildContext context,
|
||||
TranslateMouseNotifier notifier,
|
||||
Widget? child,
|
||||
) {
|
||||
if (notifier.onEnter) {
|
||||
return TranslateCellAccessory(
|
||||
viewId: bloc.cellController.viewId,
|
||||
fieldId: bloc.cellController.fieldId,
|
||||
rowId: bloc.cellController.rowId,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
).positioned(right: 0, bottom: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: TextField(
|
||||
controller: textEditingController,
|
||||
readOnly: true,
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: padding,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.cellVPadding,
|
||||
),
|
||||
child: Consumer<TranslateMouseNotifier>(
|
||||
builder: (
|
||||
BuildContext context,
|
||||
TranslateMouseNotifier notifier,
|
||||
Widget? child,
|
||||
) {
|
||||
if (notifier.onEnter) {
|
||||
return TranslateCellAccessory(
|
||||
viewId: bloc.cellController.viewId,
|
||||
fieldId: bloc.cellController.fieldId,
|
||||
rowId: bloc.cellController.rowId,
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
).positioned(right: 0, bottom: compactMode ? 4 : 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
+30
-21
@@ -21,6 +21,7 @@ class DesktopGridURLSkin extends IEditableURLCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
URLCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -28,28 +29,36 @@ class DesktopGridURLSkin extends IEditableURLCellSkin {
|
||||
) {
|
||||
return BlocSelector<URLCellBloc, URLCellState, bool>(
|
||||
selector: (state) => state.wrap,
|
||||
builder: (context, wrap) => TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
maxLines: wrap ? null : 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
builder: (context, wrap) => ValueListenableBuilder(
|
||||
valueListenable: compactModeNotifier,
|
||||
builder: (context, compactMode, _) {
|
||||
final padding = compactMode
|
||||
? GridSize.compactCellContentInsets
|
||||
: GridSize.cellContentInsets;
|
||||
return TextField(
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
maxLines: wrap ? null : 1,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: padding,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: Theme.of(context).hintColor),
|
||||
isDense: true,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: GridSize.cellContentInsets,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
errorBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: Theme.of(context).hintColor),
|
||||
isDense: true,
|
||||
),
|
||||
onTapOutside: (_) => focusNode.unfocus(),
|
||||
onTapOutside: (_) => focusNode.unfocus(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -11,6 +11,7 @@ class DesktopRowDetailCheckboxCellSkin extends IEditableCheckboxCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
CheckboxCellBloc bloc,
|
||||
CheckboxCellState state,
|
||||
) {
|
||||
|
||||
+1
@@ -24,6 +24,7 @@ class DesktopRowDetailChecklistCellSkin extends IEditableChecklistCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
ChecklistCellBloc bloc,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
|
||||
+3
-2
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/date_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -13,6 +13,7 @@ class DesktopRowDetailDateCellSkin extends IEditableDateCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
DateCellBloc bloc,
|
||||
DateCellState state,
|
||||
PopoverController popoverController,
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -11,6 +11,7 @@ class DesktopRowDetailNumberCellSkin extends IEditableNumberCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
NumberCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
|
||||
+10
-4
@@ -1,7 +1,8 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -15,10 +16,12 @@ class DesktopRowDetailRelationCellSkin extends IEditableRelationCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
final userWorkspaceBloc = context.read<UserWorkspaceBloc>();
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
@@ -27,8 +30,11 @@ class DesktopRowDetailRelationCellSkin extends IEditableRelationCellSkin {
|
||||
asBarrier: true,
|
||||
onClose: () => cellContainerNotifier.isFocus = false,
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider.value(
|
||||
value: bloc,
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: userWorkspaceBloc),
|
||||
BlocProvider.value(value: bloc),
|
||||
],
|
||||
child: const RelationCellEditor(),
|
||||
);
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||
@@ -8,6 +6,7 @@ import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../editable_cell_skeleton/select_option.dart';
|
||||
@@ -18,6 +17,7 @@ class DesktopRowDetailSelectOptionCellSkin
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
SelectOptionCellBloc bloc,
|
||||
PopoverController popoverController,
|
||||
) {
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ class DesktopRowDetailSummaryCellSkin extends IEditableSummaryCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
SummaryCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -11,6 +11,7 @@ class DesktopRowDetailTextCellSkin extends IEditableTextCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TextCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@@ -10,6 +10,7 @@ class DesktopRowDetailTimestampCellSkin extends IEditableTimestampCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TimestampCellBloc bloc,
|
||||
TimestampCellState state,
|
||||
) {
|
||||
|
||||
+2
-1
@@ -1,8 +1,8 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -14,6 +14,7 @@ class DesktopRowDetailURLSkin extends IEditableURLCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
URLCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ class DesktopRowDetailTranslateCellSkin extends IEditableTranslateCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TranslateCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
|
||||
+4
-2
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -27,6 +27,7 @@ abstract class IEditableCheckboxCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
CheckboxCellBloc bloc,
|
||||
CheckboxCellState state,
|
||||
);
|
||||
@@ -71,6 +72,7 @@ class _CheckboxCellState extends GridCellState<EditableCheckboxCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
state,
|
||||
);
|
||||
|
||||
+4
-2
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -28,6 +28,7 @@ abstract class IEditableChecklistCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
ChecklistCellBloc bloc,
|
||||
PopoverController popoverController,
|
||||
);
|
||||
@@ -72,6 +73,7 @@ class GridChecklistCellState extends GridCellState<EditableChecklistCell> {
|
||||
child: widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
_popover,
|
||||
),
|
||||
|
||||
+4
-2
@@ -1,12 +1,12 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -33,6 +33,7 @@ abstract class IEditableDateCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
DateCellBloc bloc,
|
||||
DateCellState state,
|
||||
PopoverController popoverController,
|
||||
@@ -79,6 +80,7 @@ class _DateCellState extends GridCellState<EditableDateCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
state,
|
||||
_popover,
|
||||
|
||||
+4
-2
@@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -29,6 +29,7 @@ abstract class IEditableNumberCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
NumberCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -89,6 +90,7 @@ class _NumberCellState extends GridEditableTextCell<EditableNumberCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
focusNode,
|
||||
_textEditingController,
|
||||
|
||||
+4
-2
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -28,6 +28,7 @@ abstract class IEditableRelationCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
RelationCellBloc bloc,
|
||||
RelationCellState state,
|
||||
PopoverController popoverController,
|
||||
@@ -74,6 +75,7 @@ class _RelationCellState extends GridCellState<EditableRelationCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
state,
|
||||
_popover,
|
||||
|
||||
+4
-2
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -31,6 +31,7 @@ abstract class IEditableSelectOptionCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
SelectOptionCellBloc bloc,
|
||||
PopoverController popoverController,
|
||||
);
|
||||
@@ -79,6 +80,7 @@ class _SelectOptionCellState extends GridCellState<EditableSelectOptionCell> {
|
||||
child: widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
_popover,
|
||||
),
|
||||
|
||||
+4
-3
@@ -1,6 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/summary_cell_bloc.dart';
|
||||
@@ -22,6 +19,8 @@ import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
abstract class IEditableSummaryCellSkin {
|
||||
@@ -39,6 +38,7 @@ abstract class IEditableSummaryCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
SummaryCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -98,6 +98,7 @@ class _SummaryCellState extends GridEditableTextCell<EditableSummaryCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
focusNode,
|
||||
_textEditingController,
|
||||
|
||||
+4
-2
@@ -1,11 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -29,6 +29,7 @@ abstract class IEditableTextCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TextCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -92,6 +93,7 @@ class _TextCellState extends GridEditableTextCell<EditableTextCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
focusNode,
|
||||
_textEditingController,
|
||||
|
||||
+4
-2
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -28,6 +28,7 @@ abstract class IEditableTimestampCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TimestampCellBloc bloc,
|
||||
TimestampCellState state,
|
||||
);
|
||||
@@ -74,6 +75,7 @@ class _TimestampCellState extends GridCellState<EditableTimestampCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
state,
|
||||
);
|
||||
|
||||
+4
-3
@@ -1,6 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/translate_cell_bloc.dart';
|
||||
@@ -22,6 +19,8 @@ import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
abstract class IEditableTranslateCellSkin {
|
||||
@@ -39,6 +38,7 @@ abstract class IEditableTranslateCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
TranslateCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -98,6 +98,7 @@ class _TranslateCellState extends GridEditableTextCell<EditableTranslateCell> {
|
||||
return widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
focusNode,
|
||||
_textEditingController,
|
||||
|
||||
+4
-2
@@ -4,13 +4,13 @@ import 'dart:io';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -40,6 +40,7 @@ abstract class IEditableURLCellSkin {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
CellContainerNotifier cellContainerNotifier,
|
||||
ValueNotifier<bool> compactModeNotifier,
|
||||
URLCellBloc bloc,
|
||||
FocusNode focusNode,
|
||||
TextEditingController textEditingController,
|
||||
@@ -121,6 +122,7 @@ class _GridURLCellState extends GridEditableTextCell<EditableURLCell> {
|
||||
child: widget.skin.build(
|
||||
context,
|
||||
widget.cellContainerNotifier,
|
||||
widget.databaseController.compactModeNotifier,
|
||||
cellBloc,
|
||||
focusNode,
|
||||
_textEditingController,
|
||||
|
||||
Loaded 100 of 258 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user