Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81532d014e | ||
|
|
735a09b333 | ||
|
|
d52a04a9e3 | ||
|
|
a14ced458c | ||
|
|
5250a151c8 | ||
|
|
d5a5a64fcf | ||
|
|
175c90e379 | ||
|
|
115ae27ab1 | ||
|
|
e908892b4a | ||
|
|
948d61a9e0 | ||
|
|
b5c4786896 | ||
|
|
d24f1c566a | ||
|
|
ddf68b010d |
No files matched your search
@@ -1,4 +1,12 @@
|
||||
# Release Notes
|
||||
## Version 0.6.6 - 30/07/2024
|
||||
### New Features
|
||||
- Upgrade your workspace to a premium plan to unlock more features and storage.
|
||||
- Image galleries and drag-and-drop image support in documents.
|
||||
|
||||
### Bug Fixes
|
||||
- Fix minor UI issues on Desktop and Mobile.
|
||||
|
||||
## Version 0.6.5 - 24/07/2024
|
||||
### New Features
|
||||
- Publish a Database to the Web
|
||||
|
||||
+2
-1
@@ -8,9 +8,10 @@ import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_layouts.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||
@@ -17,6 +14,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/uplo
|
||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
@@ -172,7 +171,17 @@ class EditorOperations {
|
||||
///
|
||||
/// Must call [showSlashMenu] first.
|
||||
Future<void> tapSlashMenuItemWithName(String name) async {
|
||||
final slashMenu = find
|
||||
.ancestor(
|
||||
of: find.byType(SelectionMenuItemWidget),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) => widget is Scrollable,
|
||||
),
|
||||
)
|
||||
.first;
|
||||
final slashMenuItem = find.text(name, findRichText: true);
|
||||
await tester.scrollUntilVisible(slashMenuItem, 200, scrollable: slashMenu);
|
||||
// await tester.ensureVisible(slashMenuItem);
|
||||
await tester.tapButton(slashMenuItem);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,17 +12,23 @@ import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../desktop/board/board_hide_groups_test.dart';
|
||||
|
||||
import 'base.dart';
|
||||
import 'common_operations.dart';
|
||||
|
||||
extension AppFlowySettings on WidgetTester {
|
||||
/// Open settings page
|
||||
Future<void> openSettings() async {
|
||||
final settingsDialog = find.byType(SettingsDialog);
|
||||
// tap empty area to close the settings page
|
||||
while (settingsDialog.evaluate().isNotEmpty) {
|
||||
await tapAt(Offset.zero);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
final settingsButton = find.byType(UserSettingButton);
|
||||
expect(settingsButton, findsOneWidget);
|
||||
await tapButton(settingsButton);
|
||||
final settingsDialog = find.byType(SettingsDialog);
|
||||
|
||||
expect(settingsDialog, findsOneWidget);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ SPEC CHECKSUMS:
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
|
||||
+3
@@ -23,6 +23,9 @@ class DocumentPageStyleBloc
|
||||
await event.when(
|
||||
initial: () async {
|
||||
try {
|
||||
if (view.id.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final layoutObject =
|
||||
await ViewBackendService.getView(view.id).fold(
|
||||
(s) => jsonDecode(s.extra),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:appflowy/shared/feedback_gesture_detector.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedGestureDetector extends StatefulWidget {
|
||||
const AnimatedGestureDetector({
|
||||
super.key,
|
||||
this.scaleFactor = 0.98,
|
||||
this.feedback = true,
|
||||
this.duration = const Duration(milliseconds: 100),
|
||||
this.alignment = Alignment.center,
|
||||
this.behavior = HitTestBehavior.opaque,
|
||||
required this.onTapUp,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final double scaleFactor;
|
||||
final Duration duration;
|
||||
final Alignment alignment;
|
||||
final bool feedback;
|
||||
final HitTestBehavior behavior;
|
||||
final VoidCallback onTapUp;
|
||||
|
||||
@override
|
||||
State<AnimatedGestureDetector> createState() =>
|
||||
_AnimatedGestureDetectorState();
|
||||
}
|
||||
|
||||
class _AnimatedGestureDetectorState extends State<AnimatedGestureDetector> {
|
||||
double scale = 1.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: widget.behavior,
|
||||
onTapUp: (details) {
|
||||
setState(() => scale = 1.0);
|
||||
|
||||
HapticFeedbackType.light.call();
|
||||
|
||||
widget.onTapUp();
|
||||
},
|
||||
onTapDown: (details) {
|
||||
setState(() => scale = widget.scaleFactor);
|
||||
},
|
||||
child: AnimatedScale(
|
||||
scale: scale,
|
||||
alignment: widget.alignment,
|
||||
duration: widget.duration,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -24,9 +24,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.icon_document_s,
|
||||
size: Size.square(18),
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Document),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
@@ -34,9 +35,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.icon_grid_s,
|
||||
size: Size.square(18),
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Grid),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
@@ -44,9 +46,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.icon_board_s,
|
||||
size: Size.square(18),
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Board),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
@@ -54,9 +57,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.icon_calendar_s,
|
||||
size: Size.square(18),
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Calendar),
|
||||
),
|
||||
FlowyOptionTile.text(
|
||||
@@ -64,9 +68,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.chat_ai_page_s,
|
||||
size: Size.square(18),
|
||||
size: Size.square(20),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(ViewLayoutPB.Chat),
|
||||
),
|
||||
],
|
||||
|
||||
+7
-41
@@ -1,4 +1,3 @@
|
||||
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/show_flowy_mobile_confirm_dialog.dart';
|
||||
@@ -6,6 +5,7 @@ import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -109,16 +109,6 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
await _showConfirmDialog(
|
||||
onDelete: () {
|
||||
recentViewsBloc.add(RecentViewsEvent.removeRecentViews([viewId]));
|
||||
|
||||
fToast.showToast(
|
||||
child: const _RemoveToast(),
|
||||
positionedToastBuilder: (context, child) {
|
||||
return Positioned.fill(
|
||||
top: 450,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -136,38 +126,14 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
),
|
||||
onRightButtonPressed: (context) {
|
||||
onDelete();
|
||||
|
||||
Navigator.pop(context);
|
||||
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.sideBar_removeSuccess.tr(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RemoveToast extends StatelessWidget {
|
||||
const _RemoveToast();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: const Color(0xE5171717),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.success_s,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
FlowyText.regular(
|
||||
LocaleKeys.sideBar_removeSuccess.tr(),
|
||||
fontSize: 16.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+25
-11
@@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -40,18 +41,32 @@ enum MobilePaneActionType {
|
||||
backgroundColor: const Color(0xFFFA217F),
|
||||
svg: FlowySvgs.favorite_section_remove_from_favorite_s,
|
||||
size: 24.0,
|
||||
onPressed: (context) => context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
|
||||
onPressed: (context) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.button_unfavoriteSuccessfully.tr(),
|
||||
);
|
||||
|
||||
context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view));
|
||||
},
|
||||
);
|
||||
case MobilePaneActionType.addToFavorites:
|
||||
return MobileSlideActionButton(
|
||||
backgroundColor: const Color(0xFF00C8FF),
|
||||
svg: FlowySvgs.favorite_s,
|
||||
size: 24.0,
|
||||
onPressed: (context) => context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
|
||||
onPressed: (context) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.button_favoriteSuccessfully.tr(),
|
||||
);
|
||||
|
||||
context
|
||||
.read<FavoriteBloc>()
|
||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view));
|
||||
},
|
||||
);
|
||||
case MobilePaneActionType.add:
|
||||
return MobileSlideActionButton(
|
||||
@@ -69,6 +84,7 @@ enum MobilePaneActionType {
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
useRootNavigator: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (sheetContext) {
|
||||
return AddNewPageWidgetBottomSheet(
|
||||
@@ -145,8 +161,6 @@ enum MobilePaneActionType {
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
if (view.layout != ViewLayoutPB.Chat)
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
MobileViewItemBottomSheetBodyAction.removeFromRecent,
|
||||
];
|
||||
@@ -156,7 +170,6 @@ enum MobilePaneActionType {
|
||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
MobileViewItemBottomSheetBodyAction.divider,
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -181,12 +194,13 @@ ActionPane buildEndActionPane(
|
||||
bool needSpace = true,
|
||||
MobilePageCardType? cardType,
|
||||
FolderSpaceType? spaceType,
|
||||
required double spaceRatio,
|
||||
}) {
|
||||
return ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: actions.length / 5,
|
||||
extentRatio: actions.length / spaceRatio,
|
||||
children: [
|
||||
if (needSpace) const HSpace(20),
|
||||
if (needSpace) const HSpace(60),
|
||||
...actions.map(
|
||||
(action) => action.actionButton(
|
||||
context,
|
||||
|
||||
+9
-4
@@ -70,6 +70,7 @@ Future<T?> showMobileBottomSheet<T>(
|
||||
backgroundColor ??= Theme.of(context).brightness == Brightness.light
|
||||
? const Color(0xFFF7F8FB)
|
||||
: const Color(0xFF23262B);
|
||||
barrierColor ??= Colors.black.withOpacity(0.3);
|
||||
|
||||
return showModalBottomSheet<T>(
|
||||
context: context,
|
||||
@@ -226,10 +227,14 @@ class BottomSheetHeader extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Align(
|
||||
child: FlowyText(
|
||||
title,
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 250),
|
||||
child: FlowyText(
|
||||
title,
|
||||
fontSize: 17.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDoneButton)
|
||||
|
||||
-2
@@ -100,8 +100,6 @@ class _FavoriteViews extends StatelessWidget {
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('favorite_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
|
||||
+1
@@ -72,6 +72,7 @@ class MobileFavoriteFolder extends StatelessWidget {
|
||||
MobilePaneActionType.more,
|
||||
],
|
||||
spaceType: FolderSpaceType.favorite,
|
||||
spaceRatio: 5,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -29,8 +29,6 @@ class _MobileHomeSpaceState extends State<MobileHomeSpace>
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
top: HomeSpaceViewSizes.mVerticalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_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/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -91,7 +92,12 @@ class MobileFolders extends StatelessWidget {
|
||||
children: [
|
||||
..._buildSpaceOrSection(context, state),
|
||||
const VSpace(4.0),
|
||||
const _TrashButton(),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
),
|
||||
child: _TrashButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/gesture.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart';
|
||||
@@ -113,8 +114,9 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
if (currentWorkspace == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
return AnimatedGestureDetector(
|
||||
alignment: Alignment.centerLeft,
|
||||
onTapUp: () {
|
||||
context.read<UserWorkspaceBloc>().add(
|
||||
const UserWorkspaceEvent.fetchWorkspaces(),
|
||||
);
|
||||
@@ -143,7 +145,7 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
: const HSpace(8),
|
||||
FlowyText.semibold(
|
||||
currentWorkspace.name,
|
||||
fontSize: 16.0,
|
||||
fontSize: 20.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
@@ -162,9 +164,10 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
showHeader: true,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
useRootNavigator: true,
|
||||
title: LocaleKeys.workspace_menuTitle.tr(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (_) {
|
||||
builder: (sheetContext) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
@@ -179,7 +182,7 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
currentWorkspace: currentWorkspace,
|
||||
workspaces: workspaces,
|
||||
onWorkspaceSelected: (workspace) {
|
||||
context.pop();
|
||||
Navigator.of(sheetContext).pop();
|
||||
|
||||
if (workspace == currentWorkspace) {
|
||||
return;
|
||||
|
||||
@@ -72,8 +72,6 @@ class _RecentViews extends StatelessWidget {
|
||||
child: ListView.separated(
|
||||
key: const PageStorageKey('recent_views_page_storage_key'),
|
||||
padding: EdgeInsets.only(
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
|
||||
+1
@@ -82,6 +82,7 @@ class MobileSectionFolder extends StatelessWidget {
|
||||
],
|
||||
spaceType: spaceType,
|
||||
needSpace: false,
|
||||
spaceRatio: 5,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/gesture.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
@@ -15,6 +16,7 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/time_format_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@@ -76,13 +78,14 @@ class MobileViewPage extends StatelessWidget {
|
||||
: MobilePaneActionType.addToFavorites,
|
||||
],
|
||||
cardType: type,
|
||||
spaceRatio: 4,
|
||||
),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTapUp: (_) => context.pushView(view),
|
||||
child: AnimatedGestureDetector(
|
||||
onTapUp: () => context.pushView(view),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
|
||||
Expanded(child: _buildDescription(context, state)),
|
||||
const HSpace(20.0),
|
||||
SizedBox(
|
||||
@@ -90,6 +93,7 @@ class MobileViewPage extends StatelessWidget {
|
||||
height: 60,
|
||||
child: _buildCover(context, state),
|
||||
),
|
||||
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -211,7 +215,7 @@ class MobileViewPage extends StatelessWidget {
|
||||
|
||||
Widget _buildLastViewed(BuildContext context) {
|
||||
final textColor = Theme.of(context).isLightMode
|
||||
? const Color(0xFF171717)
|
||||
? const Color(0x7F171717)
|
||||
: Colors.white.withOpacity(0.45);
|
||||
if (timestamp == null) {
|
||||
return const SizedBox.shrink();
|
||||
|
||||
@@ -50,23 +50,17 @@ class _MobileSpaceState extends State<MobileSpace> {
|
||||
MobileSpaceHeader(
|
||||
isExpanded: state.isExpanded,
|
||||
space: currentSpace,
|
||||
onAdded: () {
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.createPage(
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout: ViewLayoutPB.Document,
|
||||
index: 0,
|
||||
),
|
||||
);
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.expand(currentSpace, true),
|
||||
);
|
||||
},
|
||||
onAdded: () => _showCreatePageMenu(currentSpace),
|
||||
onPressed: () => _showSpaceMenu(context),
|
||||
),
|
||||
_Pages(
|
||||
key: ValueKey(currentSpace.id),
|
||||
space: currentSpace,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
),
|
||||
child: _Pages(
|
||||
key: ValueKey(currentSpace.id),
|
||||
space: currentSpace,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -82,6 +76,7 @@ class _MobileSpaceState extends State<MobileSpace> {
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
showDoneButton: true,
|
||||
useRootNavigator: true,
|
||||
title: LocaleKeys.space_title.tr(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (_) {
|
||||
@@ -104,6 +99,38 @@ class _MobileSpaceState extends State<MobileSpace> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCreatePageMenu(ViewPB space) {
|
||||
final title = space.name;
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
title: title,
|
||||
showDragHandle: true,
|
||||
showCloseButton: true,
|
||||
useRootNavigator: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (sheetContext) {
|
||||
return AddNewPageWidgetBottomSheet(
|
||||
view: space,
|
||||
onAction: (layout) {
|
||||
Navigator.of(sheetContext).pop();
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.createPage(
|
||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
layout: layout,
|
||||
index: 0,
|
||||
),
|
||||
);
|
||||
context.read<SpaceBloc>().add(
|
||||
SpaceEvent.expand(space, true),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Pages extends StatelessWidget {
|
||||
@@ -140,15 +167,16 @@ class _Pages extends StatelessWidget {
|
||||
onSelected: context.pushView,
|
||||
endActionPane: (context) {
|
||||
final view = context.read<ViewBloc>().state.view;
|
||||
final actions = [
|
||||
MobilePaneActionType.more,
|
||||
if (view.layout == ViewLayoutPB.Document)
|
||||
MobilePaneActionType.add,
|
||||
];
|
||||
return buildEndActionPane(
|
||||
context,
|
||||
[
|
||||
MobilePaneActionType.more,
|
||||
if (view.layout == ViewLayoutPB.Document)
|
||||
MobilePaneActionType.add,
|
||||
],
|
||||
actions,
|
||||
spaceType: spaceType,
|
||||
needSpace: false,
|
||||
spaceRatio: actions.length == 1 ? 3 : 4,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
+11
-2
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -30,6 +31,7 @@ class MobileSpaceHeader extends StatelessWidget {
|
||||
height: 48,
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
|
||||
SpaceIcon(
|
||||
dimension: 24,
|
||||
space: space,
|
||||
@@ -49,8 +51,15 @@ class MobileSpaceHeader extends StatelessWidget {
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: onAdded,
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_space_add_s,
|
||||
child: Container(
|
||||
// expand the touch area
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: HomeSpaceViewSizes.mHorizontalPadding,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.m_space_add_s,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -59,6 +59,7 @@ class _SidebarSpaceMenuItem extends StatelessWidget {
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
space.name,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
const HSpace(6.0),
|
||||
if (space.spacePermission == SpacePermission.private)
|
||||
@@ -68,16 +69,17 @@ class _SidebarSpaceMenuItem extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
iconPadding: 10,
|
||||
leftIcon: SpaceIcon(
|
||||
dimension: 24,
|
||||
space: space,
|
||||
cornerRadius: 6.0,
|
||||
),
|
||||
leftIconSize: const Size.square(20),
|
||||
leftIconSize: const Size.square(24),
|
||||
rightIcon: isSelected
|
||||
? const FlowySvg(
|
||||
FlowySvgs.workspace_selected_s,
|
||||
FlowySvgs.m_blue_check_s,
|
||||
blendMode: null,
|
||||
)
|
||||
: null,
|
||||
|
||||
@@ -23,10 +23,12 @@ class MobileSpaceTabBar extends StatelessWidget {
|
||||
final labelStyle = baseStyle?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16.0,
|
||||
height: 20 / 16,
|
||||
);
|
||||
final unselectedLabelStyle = baseStyle?.copyWith(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 15.0,
|
||||
height: 20 / 15,
|
||||
);
|
||||
|
||||
return Container(
|
||||
|
||||
+13
-10
@@ -138,17 +138,20 @@ class _WorkspaceMenuItem extends StatelessWidget {
|
||||
height: 60,
|
||||
showTopBorder: showTopBorder,
|
||||
showBottomBorder: false,
|
||||
leftIcon: WorkspaceIcon(
|
||||
enableEdit: false,
|
||||
iconSize: 26,
|
||||
fontSize: 16.0,
|
||||
workspace: workspace,
|
||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
workspace.workspaceId,
|
||||
result.emoji,
|
||||
leftIcon: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: WorkspaceIcon(
|
||||
enableEdit: false,
|
||||
iconSize: 26,
|
||||
fontSize: 16.0,
|
||||
workspace: workspace,
|
||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
workspace.workspaceId,
|
||||
result.emoji,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: workspace.workspaceId == currentWorkspace.workspaceId
|
||||
? const FlowySvg(
|
||||
|
||||
@@ -50,6 +50,9 @@ class MobileBottomNavigationBar extends StatelessWidget {
|
||||
final backgroundColor = isLightMode
|
||||
? Colors.white.withOpacity(0.95)
|
||||
: const Color(0xFF23262B).withOpacity(0.95);
|
||||
final borderColor = isLightMode
|
||||
? const Color(0x141F2329)
|
||||
: const Color(0xFF23262B).withOpacity(0.5);
|
||||
return Scaffold(
|
||||
body: navigationShell,
|
||||
extendBody: true,
|
||||
@@ -62,9 +65,7 @@ class MobileBottomNavigationBar extends StatelessWidget {
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: isLightMode
|
||||
? Border(
|
||||
top: BorderSide(color: Theme.of(context).dividerColor),
|
||||
)
|
||||
? Border(top: BorderSide(color: borderColor))
|
||||
: null,
|
||||
color: backgroundColor,
|
||||
),
|
||||
|
||||
@@ -300,7 +300,7 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
|
||||
)
|
||||
: Opacity(
|
||||
opacity: 0.7,
|
||||
child: widget.view.defaultIcon(),
|
||||
child: widget.view.defaultIcon(size: const Size.square(18)),
|
||||
);
|
||||
return SizedBox(width: 18.0, child: icon);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ class ChatInputBloc extends Bloc<ChatInputEvent, ChatInputState> {
|
||||
: listener = LocalLLMListener(),
|
||||
super(const ChatInputState(aiType: _AppFlowyAI())) {
|
||||
listener.start(
|
||||
stateCallback: (pluginState) {
|
||||
chatStateCallback: (aiState) {
|
||||
if (!isClosed) {
|
||||
add(ChatInputEvent.updateState(pluginState));
|
||||
add(ChatInputEvent.updateState(aiState));
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -37,18 +37,26 @@ class ChatInputBloc extends Bloc<ChatInputEvent, ChatInputState> {
|
||||
) async {
|
||||
await event.when(
|
||||
started: () async {
|
||||
final result = await ChatEventGetLocalAIPluginState().send();
|
||||
final result = await ChatEventGetLocalAIChatState().send();
|
||||
result.fold(
|
||||
(pluginState) {
|
||||
(aiState) {
|
||||
if (!isClosed) {
|
||||
add(ChatInputEvent.updateState(pluginState));
|
||||
add(
|
||||
ChatInputEvent.updateState(aiState),
|
||||
);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err.toString()),
|
||||
(err) {
|
||||
Log.error(err.toString());
|
||||
},
|
||||
);
|
||||
},
|
||||
updateState: (LocalAIPluginStatePB aiPluginState) {
|
||||
emit(const ChatInputState(aiType: _AppFlowyAI()));
|
||||
updateState: (aiState) {
|
||||
if (aiState.pluginState.state == RunningStatePB.Running) {
|
||||
emit(const ChatInputState(aiType: _LocalAI()));
|
||||
} else {
|
||||
emit(const ChatInputState(aiType: _AppFlowyAI()));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -57,8 +65,8 @@ class ChatInputBloc extends Bloc<ChatInputEvent, ChatInputState> {
|
||||
@freezed
|
||||
class ChatInputEvent with _$ChatInputEvent {
|
||||
const factory ChatInputEvent.started() = _Started;
|
||||
const factory ChatInputEvent.updateState(LocalAIPluginStatePB aiPluginState) =
|
||||
_UpdatePluginState;
|
||||
const factory ChatInputEvent.updateState(LocalAIChatPB aiState) =
|
||||
_UpdateAIState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
||||
@@ -23,7 +23,7 @@ import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart' show Chat;
|
||||
|
||||
import 'presentation/chat_input.dart';
|
||||
import 'presentation/chat_input/chat_input.dart';
|
||||
import 'presentation/chat_popmenu.dart';
|
||||
import 'presentation/chat_theme.dart';
|
||||
import 'presentation/chat_user_invalid_message.dart';
|
||||
@@ -82,7 +82,9 @@ class AIChatPage extends StatelessWidget {
|
||||
userProfile: userProfile,
|
||||
)..add(const ChatEvent.initialLoad()),
|
||||
),
|
||||
BlocProvider(create: (_) => ChatInputBloc()),
|
||||
BlocProvider(
|
||||
create: (_) => ChatInputBloc()..add(const ChatInputEvent.started()),
|
||||
),
|
||||
],
|
||||
child: BlocListener<ChatFileBloc, ChatFileState>(
|
||||
listenWhen: (previous, current) =>
|
||||
@@ -391,36 +393,39 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
padding: AIChatUILayout.safeAreaInsets(context),
|
||||
child: BlocBuilder<ChatInputBloc, ChatInputState>(
|
||||
builder: (context, state) {
|
||||
return state.aiType.when(
|
||||
appflowyAI: () => Column(
|
||||
children: [
|
||||
BlocSelector<ChatBloc, ChatState, LoadingState>(
|
||||
selector: (state) => state.streamingStatus,
|
||||
builder: (context, state) {
|
||||
return ChatInput(
|
||||
chatId: widget.view.id,
|
||||
onSendPressed: (message) =>
|
||||
onSendPressed(context, message.text),
|
||||
isStreaming: state != const LoadingState.finish(),
|
||||
onStopStreaming: () {
|
||||
context
|
||||
.read<ChatBloc>()
|
||||
.add(const ChatEvent.stopStream());
|
||||
},
|
||||
);
|
||||
},
|
||||
final hintText = state.aiType.when(
|
||||
appflowyAI: () => LocaleKeys.chat_inputMessageHint.tr(),
|
||||
localAI: () => LocaleKeys.chat_inputLocalAIMessageHint.tr(),
|
||||
);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
BlocSelector<ChatBloc, ChatState, LoadingState>(
|
||||
selector: (state) => state.streamingStatus,
|
||||
builder: (context, state) {
|
||||
return ChatInput(
|
||||
chatId: widget.view.id,
|
||||
onSendPressed: (message) =>
|
||||
onSendPressed(context, message.text),
|
||||
isStreaming: state != const LoadingState.finish(),
|
||||
onStopStreaming: () {
|
||||
context
|
||||
.read<ChatBloc>()
|
||||
.add(const ChatEvent.stopStream());
|
||||
},
|
||||
hintText: hintText,
|
||||
);
|
||||
},
|
||||
),
|
||||
const VSpace(6),
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
child: FlowyText(
|
||||
LocaleKeys.chat_aiMistakePrompt.tr(),
|
||||
fontSize: 12,
|
||||
),
|
||||
const VSpace(6),
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
child: FlowyText(
|
||||
LocaleKeys.chat_aiMistakePrompt.tr(),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
localAI: () => const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -4,7 +4,7 @@ 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/presentation/chat_avatar.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_input.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_input/chat_input.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_popmenu.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
abstract class ChatActionMenuItem {
|
||||
String get title;
|
||||
}
|
||||
|
||||
abstract class ChatActionHandler {
|
||||
List<ChatActionMenuItem> get items;
|
||||
void onEnter();
|
||||
void onSelected(ChatActionMenuItem item);
|
||||
void onExit();
|
||||
}
|
||||
|
||||
abstract class ChatAnchor {
|
||||
GlobalKey get anchorKey;
|
||||
LayerLink get layerLink;
|
||||
}
|
||||
|
||||
const int _itemHeight = 34;
|
||||
const int _itemVerticalPadding = 4;
|
||||
|
||||
class ChatActionsMenu {
|
||||
ChatActionsMenu({
|
||||
required this.anchor,
|
||||
required this.context,
|
||||
required this.handler,
|
||||
required this.style,
|
||||
});
|
||||
|
||||
final BuildContext context;
|
||||
final ChatAnchor anchor;
|
||||
final ChatActionsMenuStyle style;
|
||||
final ChatActionHandler handler;
|
||||
|
||||
OverlayEntry? _overlayEntry;
|
||||
|
||||
void dismiss() {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
handler.onExit();
|
||||
}
|
||||
|
||||
void show() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _show());
|
||||
}
|
||||
|
||||
void _show() {
|
||||
if (_overlayEntry != null) {
|
||||
dismiss();
|
||||
}
|
||||
|
||||
if (anchor.anchorKey.currentContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler.onEnter();
|
||||
|
||||
final height = handler.items.length * (_itemHeight + _itemVerticalPadding);
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (context) => Stack(
|
||||
children: [
|
||||
CompositedTransformFollower(
|
||||
link: anchor.layerLink,
|
||||
showWhenUnlinked: false,
|
||||
offset: Offset(0, -height - 4),
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 200,
|
||||
maxWidth: 200,
|
||||
maxHeight: 200,
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
),
|
||||
child: ActionList(
|
||||
handler: handler,
|
||||
onDismiss: () => dismiss(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionItem extends StatelessWidget {
|
||||
const _ActionItem({
|
||||
required this.item,
|
||||
required this.onTap,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
final ChatActionMenuItem item;
|
||||
final VoidCallback? onTap;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: _itemHeight.toDouble(),
|
||||
padding: const EdgeInsets.symmetric(vertical: _itemVerticalPadding / 2.0),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.primary.withOpacity(0.1)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
child: FlowyButton(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 6),
|
||||
iconPadding: 10.0,
|
||||
text: FlowyText.regular(
|
||||
item.title,
|
||||
),
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ActionList extends StatefulWidget {
|
||||
const ActionList({super.key, required this.handler, required this.onDismiss});
|
||||
|
||||
final ChatActionHandler handler;
|
||||
final VoidCallback? onDismiss;
|
||||
|
||||
@override
|
||||
State<ActionList> createState() => _ActionListState();
|
||||
}
|
||||
|
||||
class _ActionListState extends State<ActionList> {
|
||||
final FocusScopeNode _focusNode =
|
||||
FocusScopeNode(debugLabel: 'ChatActionsMenu');
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_focusNode.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleKeyPress(event) {
|
||||
setState(() {
|
||||
// ignore: deprecated_member_use
|
||||
if (event is KeyDownEvent || event is RawKeyDownEvent) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||
_selectedIndex = (_selectedIndex + 1) % widget.handler.items.length;
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||
_selectedIndex = (_selectedIndex - 1 + widget.handler.items.length) %
|
||||
widget.handler.items.length;
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||
widget.handler.onSelected(widget.handler.items[_selectedIndex]);
|
||||
widget.onDismiss?.call();
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.escape) {
|
||||
widget.onDismiss?.call();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FocusScope(
|
||||
node: _focusNode,
|
||||
onKey: (node, event) {
|
||||
_handleKeyPress(event);
|
||||
return KeyEventResult.handled;
|
||||
},
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: widget.handler.items.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final ChatActionMenuItem item = entry.value;
|
||||
return _ActionItem(
|
||||
item: item,
|
||||
onTap: () {
|
||||
widget.handler.onSelected(item);
|
||||
widget.onDismiss?.call();
|
||||
},
|
||||
isSelected: _selectedIndex == index,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatActionsMenuStyle {
|
||||
ChatActionsMenuStyle({
|
||||
required this.backgroundColor,
|
||||
required this.groupTextColor,
|
||||
required this.menuItemTextColor,
|
||||
required this.menuItemSelectedColor,
|
||||
required this.menuItemSelectedTextColor,
|
||||
});
|
||||
|
||||
const ChatActionsMenuStyle.light()
|
||||
: backgroundColor = Colors.white,
|
||||
groupTextColor = const Color(0xFF555555),
|
||||
menuItemTextColor = const Color(0xFF333333),
|
||||
menuItemSelectedColor = const Color(0xFFE0F8FF),
|
||||
menuItemSelectedTextColor = const Color.fromARGB(255, 56, 91, 247);
|
||||
|
||||
const ChatActionsMenuStyle.dark()
|
||||
: backgroundColor = const Color(0xFF282E3A),
|
||||
groupTextColor = const Color(0xFFBBC3CD),
|
||||
menuItemTextColor = const Color(0xFFBBC3CD),
|
||||
menuItemSelectedColor = const Color(0xFF00BCF0),
|
||||
menuItemSelectedTextColor = const Color(0xFF131720);
|
||||
|
||||
final Color backgroundColor;
|
||||
final Color groupTextColor;
|
||||
final Color menuItemTextColor;
|
||||
final Color menuItemSelectedColor;
|
||||
final Color menuItemSelectedTextColor;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatInputAccessoryButton extends StatelessWidget {
|
||||
const ChatInputAccessoryButton({
|
||||
required this.onSendPressed,
|
||||
required this.onStopStreaming,
|
||||
required this.isStreaming,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final void Function() onSendPressed;
|
||||
final void Function() onStopStreaming;
|
||||
final bool isStreaming;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isStreaming) {
|
||||
return FlowyIconButton(
|
||||
width: 36,
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.ai_stream_stop_s,
|
||||
size: const Size.square(28),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: onStopStreaming,
|
||||
radius: BorderRadius.circular(18),
|
||||
fillColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
);
|
||||
} else {
|
||||
return FlowyIconButton(
|
||||
width: 36,
|
||||
fillColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
radius: BorderRadius.circular(18),
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.send_s,
|
||||
size: const Size.square(24),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: onSendPressed,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_inline_action_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatTextFieldInterceptor {
|
||||
String previosText = "";
|
||||
|
||||
ChatActionHandler? onTextChanged(
|
||||
String text,
|
||||
TextEditingController textController,
|
||||
FocusNode textFieldFocusNode,
|
||||
) {
|
||||
if (previosText == "/" && text == "/ ") {
|
||||
final handler = IndexActionHandler(
|
||||
textController: textController,
|
||||
textFieldFocusNode: textFieldFocusNode,
|
||||
) as ChatActionHandler;
|
||||
return handler;
|
||||
}
|
||||
previosText = text;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class FixGrammarMenuItem extends ChatActionMenuItem {
|
||||
@override
|
||||
String get title => "Fix Grammar";
|
||||
}
|
||||
|
||||
class ImproveWritingMenuItem extends ChatActionMenuItem {
|
||||
@override
|
||||
String get title => "Improve Writing";
|
||||
}
|
||||
|
||||
class ChatWithFileMenuItem extends ChatActionMenuItem {
|
||||
@override
|
||||
String get title => "Chat With PDF";
|
||||
}
|
||||
|
||||
class IndexActionHandler extends ChatActionHandler {
|
||||
IndexActionHandler({
|
||||
required this.textController,
|
||||
required this.textFieldFocusNode,
|
||||
});
|
||||
|
||||
final TextEditingController textController;
|
||||
final FocusNode textFieldFocusNode;
|
||||
|
||||
@override
|
||||
List<ChatActionMenuItem> get items => [
|
||||
ChatWithFileMenuItem(),
|
||||
FixGrammarMenuItem(),
|
||||
ImproveWritingMenuItem(),
|
||||
];
|
||||
|
||||
@override
|
||||
void onSelected(ChatActionMenuItem item) {
|
||||
textController.clear();
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => textFieldFocusNode.requestFocus(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onExit() {
|
||||
if (!textFieldFocusNode.hasFocus) {
|
||||
textFieldFocusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onEnter() {
|
||||
if (textFieldFocusNode.hasFocus) {
|
||||
textFieldFocusNode.unfocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
-104
@@ -1,14 +1,13 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/presentation/chat_inline_action_menu.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||
|
||||
import 'chat_accessory_button.dart';
|
||||
|
||||
class ChatInput extends StatefulWidget {
|
||||
/// Creates [ChatInput] widget.
|
||||
const ChatInput({
|
||||
@@ -20,6 +19,7 @@ class ChatInput extends StatefulWidget {
|
||||
this.options = const InputOptions(),
|
||||
required this.isStreaming,
|
||||
required this.onStopStreaming,
|
||||
required this.hintText,
|
||||
});
|
||||
|
||||
final bool? isAttachmentUploading;
|
||||
@@ -29,6 +29,7 @@ class ChatInput extends StatefulWidget {
|
||||
final InputOptions options;
|
||||
final String chatId;
|
||||
final bool isStreaming;
|
||||
final String hintText;
|
||||
|
||||
@override
|
||||
State<ChatInput> createState() => _ChatInputState();
|
||||
@@ -36,6 +37,11 @@ class ChatInput extends StatefulWidget {
|
||||
|
||||
/// [ChatInput] widget state.
|
||||
class _ChatInputState extends State<ChatInput> {
|
||||
final GlobalKey _textFieldKey = GlobalKey();
|
||||
final LayerLink _layerLink = LayerLink();
|
||||
// final ChatTextFieldInterceptor _textFieldInterceptor =
|
||||
// ChatTextFieldInterceptor();
|
||||
|
||||
late final _inputFocusNode = FocusNode(
|
||||
onKeyEvent: (node, event) {
|
||||
if (event.physicalKey == PhysicalKeyboardKey.enter &&
|
||||
@@ -59,9 +65,9 @@ class _ChatInputState extends State<ChatInput> {
|
||||
}
|
||||
},
|
||||
);
|
||||
late TextEditingController _textController;
|
||||
|
||||
bool _sendButtonVisible = false;
|
||||
late TextEditingController _textController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -71,33 +77,15 @@ class _ChatInputState extends State<ChatInput> {
|
||||
_handleSendButtonVisibilityModeChange();
|
||||
}
|
||||
|
||||
void _handleSendButtonVisibilityModeChange() {
|
||||
_textController.removeListener(_handleTextControllerChange);
|
||||
_sendButtonVisible =
|
||||
_textController.text.trim() != '' || widget.isStreaming;
|
||||
_textController.addListener(_handleTextControllerChange);
|
||||
@override
|
||||
void dispose() {
|
||||
_inputFocusNode.dispose();
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleSendPressed() {
|
||||
final trimmedText = _textController.text.trim();
|
||||
if (trimmedText != '') {
|
||||
final partialText = types.PartialText(text: trimmedText);
|
||||
widget.onSendPressed(partialText);
|
||||
|
||||
_textController.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTextControllerChange() {
|
||||
if (_textController.value.isComposingRangeValid) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_sendButtonVisible = _textController.text.trim() != '';
|
||||
});
|
||||
}
|
||||
|
||||
Widget _inputBuilder() {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const textPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 6);
|
||||
const buttonPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 6);
|
||||
const inputPadding = EdgeInsets.all(6);
|
||||
@@ -128,27 +116,78 @@ class _ChatInputState extends State<ChatInput> {
|
||||
);
|
||||
}
|
||||
|
||||
Padding _inputTextField(EdgeInsets textPadding) {
|
||||
return Padding(
|
||||
padding: textPadding,
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
focusNode: _inputFocusNode,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: LocaleKeys.chat_inputMessageHint.tr(),
|
||||
hintStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor.withOpacity(0.5),
|
||||
void _handleSendButtonVisibilityModeChange() {
|
||||
_textController.removeListener(_handleTextControllerChange);
|
||||
_sendButtonVisible =
|
||||
_textController.text.trim() != '' || widget.isStreaming;
|
||||
_textController.addListener(_handleTextControllerChange);
|
||||
}
|
||||
|
||||
void _handleSendPressed() {
|
||||
final trimmedText = _textController.text.trim();
|
||||
if (trimmedText != '') {
|
||||
final partialText = types.PartialText(text: trimmedText);
|
||||
widget.onSendPressed(partialText);
|
||||
|
||||
_textController.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleTextControllerChange() {
|
||||
if (_textController.value.isComposingRangeValid) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_sendButtonVisible = _textController.text.trim() != '';
|
||||
});
|
||||
}
|
||||
|
||||
Widget _inputTextField(EdgeInsets textPadding) {
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: Padding(
|
||||
padding: textPadding,
|
||||
child: TextField(
|
||||
key: _textFieldKey,
|
||||
controller: _textController,
|
||||
focusNode: _inputFocusNode,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: widget.hintText,
|
||||
hintStyle: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
maxLines: 10,
|
||||
minLines: 1,
|
||||
// onChanged: (text) {
|
||||
// final handler = _textFieldInterceptor.onTextChanged(
|
||||
// text,
|
||||
// _textController,
|
||||
// _inputFocusNode,
|
||||
// );
|
||||
// // If the handler is not null, it means that the text has been
|
||||
// // recognized as a command.
|
||||
// if (handler != null) {
|
||||
// ChatActionsMenu(
|
||||
// anchor: ChatInputAnchor(
|
||||
// anchorKey: _textFieldKey,
|
||||
// layerLink: _layerLink,
|
||||
// ),
|
||||
// handler: handler,
|
||||
// context: context,
|
||||
// style: Theme.of(context).brightness == Brightness.dark
|
||||
// ? const ChatActionsMenuStyle.dark()
|
||||
// : const ChatActionsMenuStyle.light(),
|
||||
// ).show();
|
||||
// }
|
||||
// },
|
||||
),
|
||||
style: TextStyle(
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
keyboardType: TextInputType.multiline,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
maxLines: 10,
|
||||
minLines: 1,
|
||||
onChanged: (_) {},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -162,16 +201,14 @@ class _ChatInputState extends State<ChatInput> {
|
||||
visible: _sendButtonVisible,
|
||||
child: Padding(
|
||||
padding: buttonPadding,
|
||||
child: AccessoryButton(
|
||||
child: ChatInputAccessoryButton(
|
||||
onSendPressed: () {
|
||||
if (!widget.isStreaming) {
|
||||
widget.onStopStreaming();
|
||||
_handleSendPressed();
|
||||
}
|
||||
},
|
||||
onStopStreaming: () {
|
||||
widget.onStopStreaming();
|
||||
},
|
||||
onStopStreaming: () => widget.onStopStreaming(),
|
||||
isStreaming: widget.isStreaming,
|
||||
),
|
||||
),
|
||||
@@ -184,64 +221,20 @@ class _ChatInputState extends State<ChatInput> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_handleSendButtonVisibilityModeChange();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_inputFocusNode.dispose();
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: () => _inputFocusNode.requestFocus(),
|
||||
child: _inputBuilder(),
|
||||
);
|
||||
}
|
||||
|
||||
final isMobile = defaultTargetPlatform == TargetPlatform.android ||
|
||||
defaultTargetPlatform == TargetPlatform.iOS;
|
||||
|
||||
class AccessoryButton extends StatelessWidget {
|
||||
const AccessoryButton({
|
||||
required this.onSendPressed,
|
||||
required this.onStopStreaming,
|
||||
required this.isStreaming,
|
||||
super.key,
|
||||
class ChatInputAnchor extends ChatAnchor {
|
||||
ChatInputAnchor({
|
||||
required this.anchorKey,
|
||||
required this.layerLink,
|
||||
});
|
||||
|
||||
final void Function() onSendPressed;
|
||||
final void Function() onStopStreaming;
|
||||
final bool isStreaming;
|
||||
@override
|
||||
final GlobalKey<State<StatefulWidget>> anchorKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isStreaming) {
|
||||
return FlowyIconButton(
|
||||
width: 36,
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.ai_stream_stop_s,
|
||||
size: const Size.square(28),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: onStopStreaming,
|
||||
radius: BorderRadius.circular(18),
|
||||
fillColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
);
|
||||
} else {
|
||||
return FlowyIconButton(
|
||||
width: 36,
|
||||
fillColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
radius: BorderRadius.circular(18),
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.send_s,
|
||||
size: const Size.square(24),
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
onPressed: onSendPressed,
|
||||
);
|
||||
}
|
||||
}
|
||||
final LayerLink layerLink;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'chat_input.dart';
|
||||
import 'chat_input/chat_input.dart';
|
||||
|
||||
class ChatWelcomePage extends StatelessWidget {
|
||||
ChatWelcomePage({required this.onSelectedQuestion, super.key});
|
||||
|
||||
+8
-1
@@ -1,11 +1,14 @@
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:markdown_widget/markdown_widget.dart';
|
||||
|
||||
import 'selectable_highlight.dart';
|
||||
@@ -30,7 +33,11 @@ class AIMarkdownText extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
switch (type) {
|
||||
case AIMarkdownType.appflowyEditor:
|
||||
return _AppFlowyEditorMarkdown(markdown: markdown);
|
||||
return BlocProvider(
|
||||
create: (context) => DocumentPageStyleBloc(view: ViewPB())
|
||||
..add(const DocumentPageStyleEvent.initial()),
|
||||
child: _AppFlowyEditorMarkdown(markdown: markdown),
|
||||
);
|
||||
case AIMarkdownType.markdownWidget:
|
||||
return _ThirdPartyMarkdown(markdown: markdown);
|
||||
}
|
||||
|
||||
+15
-12
@@ -90,19 +90,22 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
_buildCover(context, state),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: iconAndTitle,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildCover(context, state),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||
child: iconAndTitle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
|
||||
@visibleForTesting
|
||||
class ImageRender extends StatelessWidget {
|
||||
const ImageRender({
|
||||
super.key,
|
||||
required this.image,
|
||||
this.userProfile,
|
||||
this.fit = BoxFit.cover,
|
||||
this.borderRadius = Corners.s6Border,
|
||||
});
|
||||
|
||||
final ImageBlockData image;
|
||||
final UserProfilePB? userProfile;
|
||||
final BoxFit fit;
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = switch (image.type) {
|
||||
CustomImageType.internal || CustomImageType.external => FlowyNetworkImage(
|
||||
url: image.url,
|
||||
userProfilePB: userProfile,
|
||||
fit: fit,
|
||||
),
|
||||
CustomImageType.local => Image.file(File(image.url), fit: fit),
|
||||
};
|
||||
|
||||
return Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(borderRadius: borderRadius),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
+111
-154
@@ -7,13 +7,13 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/multi_image_layouts.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
|
||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide ResizableImage;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -25,25 +25,10 @@ import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../image_render.dart';
|
||||
|
||||
const _thumbnailItemSize = 100.0;
|
||||
|
||||
abstract class ImageBlockMultiLayout extends StatefulWidget {
|
||||
const ImageBlockMultiLayout({
|
||||
super.key,
|
||||
required this.node,
|
||||
required this.editorState,
|
||||
required this.images,
|
||||
required this.indexNotifier,
|
||||
required this.isLocalMode,
|
||||
});
|
||||
|
||||
final Node node;
|
||||
final EditorState editorState;
|
||||
final List<ImageBlockData> images;
|
||||
final ValueNotifier<int> indexNotifier;
|
||||
final bool isLocalMode;
|
||||
}
|
||||
|
||||
class ImageBrowserLayout extends ImageBlockMultiLayout {
|
||||
const ImageBrowserLayout({
|
||||
super.key,
|
||||
@@ -97,115 +82,118 @@ class _ImageBrowserLayoutState extends State<ImageBrowserLayout> {
|
||||
(constraints.maxWidth / (_thumbnailItemSize + 4)).floor();
|
||||
final items = widget.images.take(maxItems).toList();
|
||||
|
||||
return Wrap(
|
||||
children: items.mapIndexed((index, image) {
|
||||
final isLast = items.last == image;
|
||||
final amountLeft = widget.images.length - items.length;
|
||||
if (isLast && amountLeft > 0) {
|
||||
return Center(
|
||||
child: Wrap(
|
||||
children: items.mapIndexed((index, image) {
|
||||
final isLast = items.last == image;
|
||||
final amountLeft = widget.images.length - items.length;
|
||||
if (isLast && amountLeft > 0) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => _openInteractiveViewer(
|
||||
context,
|
||||
maxItems - 1,
|
||||
),
|
||||
child: Container(
|
||||
width: _thumbnailItemSize,
|
||||
height: _thumbnailItemSize,
|
||||
padding: const EdgeInsets.all(2),
|
||||
margin: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: Corners.s8Border,
|
||||
border: Border.all(
|
||||
width: 2,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: Corners.s6Border,
|
||||
image: image.type == CustomImageType.local
|
||||
? DecorationImage(
|
||||
image: FileImage(File(image.url)),
|
||||
fit: BoxFit.cover,
|
||||
opacity: 0.5,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (image.type != CustomImageType.local)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
child: FlowyNetworkImage(
|
||||
url: image.url,
|
||||
userProfilePB: _userProfile,
|
||||
),
|
||||
),
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
),
|
||||
child: Center(
|
||||
child: FlowyText(
|
||||
'+$amountLeft',
|
||||
color: AFThemeExtension.of(context)
|
||||
.strongText,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => _openInteractiveViewer(
|
||||
context,
|
||||
maxItems - 1,
|
||||
),
|
||||
child: Container(
|
||||
width: _thumbnailItemSize,
|
||||
height: _thumbnailItemSize,
|
||||
padding: const EdgeInsets.all(2),
|
||||
margin: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: Corners.s8Border,
|
||||
border: Border.all(
|
||||
width: 2,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: Corners.s6Border,
|
||||
image: image.type == CustomImageType.local
|
||||
? DecorationImage(
|
||||
image: FileImage(File(image.url)),
|
||||
fit: BoxFit.cover,
|
||||
opacity: 0.5,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
if (image.type != CustomImageType.local)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
child: FlowyNetworkImage(
|
||||
url: image.url,
|
||||
userProfilePB: _userProfile,
|
||||
),
|
||||
),
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.5),
|
||||
),
|
||||
child: Center(
|
||||
child: FlowyText(
|
||||
'+$amountLeft',
|
||||
color: AFThemeExtension.of(context)
|
||||
.strongText,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: () => widget.onIndexChanged(index),
|
||||
child: ThumbnailItem(
|
||||
images: widget.images,
|
||||
index: index,
|
||||
selectedIndex: widget.indexNotifier.value,
|
||||
userProfile: _userProfile,
|
||||
onDeleted: () async {
|
||||
final transaction =
|
||||
widget.editorState.transaction;
|
||||
|
||||
final images = widget.images.toList();
|
||||
images.removeAt(index);
|
||||
|
||||
transaction.updateNode(
|
||||
widget.node,
|
||||
{
|
||||
MultiImageBlockKeys.images:
|
||||
images.map((e) => e.toJson()).toList(),
|
||||
MultiImageBlockKeys.layout: widget.node
|
||||
.attributes[MultiImageBlockKeys.layout],
|
||||
},
|
||||
);
|
||||
|
||||
await widget.editorState.apply(transaction);
|
||||
|
||||
widget.onIndexChanged(
|
||||
widget.indexNotifier.value > 0
|
||||
? widget.indexNotifier.value - 1
|
||||
: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => widget.onIndexChanged(index),
|
||||
child: ThumbnailItem(
|
||||
images: widget.images,
|
||||
index: index,
|
||||
selectedIndex: widget.indexNotifier.value,
|
||||
userProfile: _userProfile,
|
||||
onDeleted: () async {
|
||||
final transaction = widget.editorState.transaction;
|
||||
|
||||
final images = widget.images.toList();
|
||||
images.removeAt(index);
|
||||
|
||||
transaction.updateNode(
|
||||
widget.node,
|
||||
{
|
||||
MultiImageBlockKeys.images:
|
||||
images.map((e) => e.toJson()).toList(),
|
||||
MultiImageBlockKeys.layout: widget.node
|
||||
.attributes[MultiImageBlockKeys.layout],
|
||||
},
|
||||
);
|
||||
|
||||
await widget.editorState.apply(transaction);
|
||||
|
||||
widget.onIndexChanged(
|
||||
widget.indexNotifier.value > 0
|
||||
? widget.indexNotifier.value - 1
|
||||
: 0,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
}).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -324,7 +312,8 @@ class _ImageBrowserLayoutState extends State<ImageBrowserLayout> {
|
||||
|
||||
transaction.updateNode(widget.node, {
|
||||
MultiImageBlockKeys.images: imagesJson,
|
||||
MultiImageBlockKeys.layout: MultiImageLayout.browser.toIntValue(),
|
||||
MultiImageBlockKeys.layout:
|
||||
widget.node.attributes[MultiImageBlockKeys.layout],
|
||||
});
|
||||
|
||||
await widget.editorState.apply(transaction);
|
||||
@@ -417,35 +406,3 @@ class _ThumbnailItemState extends State<ThumbnailItem> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class ImageRender extends StatelessWidget {
|
||||
const ImageRender({
|
||||
super.key,
|
||||
required this.image,
|
||||
this.userProfile,
|
||||
this.fit = BoxFit.cover,
|
||||
});
|
||||
|
||||
final ImageBlockData image;
|
||||
final UserProfilePB? userProfile;
|
||||
final BoxFit fit;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = switch (image.type) {
|
||||
CustomImageType.internal || CustomImageType.external => FlowyNetworkImage(
|
||||
url: image.url,
|
||||
userProfilePB: userProfile,
|
||||
fit: fit,
|
||||
),
|
||||
CustomImageType.local => Image.file(File(image.url), fit: fit),
|
||||
};
|
||||
|
||||
return Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: const BoxDecoration(borderRadius: Corners.s6Border),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/multi_image_layouts.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ImageGridLayout extends ImageBlockMultiLayout {
|
||||
const ImageGridLayout({
|
||||
super.key,
|
||||
required super.node,
|
||||
required super.editorState,
|
||||
required super.images,
|
||||
required super.indexNotifier,
|
||||
required super.isLocalMode,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ImageGridLayout> createState() => _ImageGridLayoutState();
|
||||
}
|
||||
|
||||
class _ImageGridLayoutState extends State<ImageGridLayout> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StaggeredGridBuilder(
|
||||
images: widget.images,
|
||||
onImageDoubleTapped: (index) {
|
||||
_openInteractiveViewer(context, index);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openInteractiveViewer(BuildContext context, int index) => showDialog(
|
||||
context: context,
|
||||
builder: (_) => InteractiveImageViewer(
|
||||
userProfile: context.read<DocumentBloc>().state.userProfilePB,
|
||||
imageProvider: AFBlockImageProvider(
|
||||
images: widget.images,
|
||||
initialIndex: index,
|
||||
onDeleteImage: (index) async {
|
||||
final transaction = widget.editorState.transaction;
|
||||
final newImages = widget.images.toList();
|
||||
newImages.removeAt(index);
|
||||
|
||||
if (newImages.isNotEmpty) {
|
||||
transaction.updateNode(
|
||||
widget.node,
|
||||
{
|
||||
MultiImageBlockKeys.images:
|
||||
newImages.map((e) => e.toJson()).toList(),
|
||||
MultiImageBlockKeys.layout:
|
||||
widget.node.attributes[MultiImageBlockKeys.layout],
|
||||
},
|
||||
);
|
||||
} else {
|
||||
transaction.deleteNode(widget.node);
|
||||
}
|
||||
|
||||
await widget.editorState.apply(transaction);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Draws a staggered grid of images, where the pattern is based
|
||||
/// on the amount of images to fill the grid at all times.
|
||||
///
|
||||
/// They will be alternating depending on the current index of the images, such that
|
||||
/// the layout is reversed in odd segments.
|
||||
///
|
||||
/// If there are 4 images in the last segment, this layout will be used:
|
||||
/// ┌─────┐┌─┐┌─┐
|
||||
/// │ │└─┘└─┘
|
||||
/// │ │┌────┐
|
||||
/// └─────┘└────┘
|
||||
///
|
||||
/// If there are 3 images in the last segment, this layout will be used:
|
||||
/// ┌─────┐┌────┐
|
||||
/// │ │└────┘
|
||||
/// │ │┌────┐
|
||||
/// └─────┘└────┘
|
||||
///
|
||||
/// If there are 2 images in the last segment, this layout will be used:
|
||||
/// ┌─────┐┌─────┐
|
||||
/// │ ││ │
|
||||
/// └─────┘└─────┘
|
||||
///
|
||||
/// If there is 1 image in the last segment, this layout will be used:
|
||||
/// ┌──────────┐
|
||||
/// │ │
|
||||
/// └──────────┘
|
||||
class StaggeredGridBuilder extends StatefulWidget {
|
||||
const StaggeredGridBuilder({
|
||||
super.key,
|
||||
required this.images,
|
||||
required this.onImageDoubleTapped,
|
||||
});
|
||||
|
||||
final List<ImageBlockData> images;
|
||||
final void Function(int) onImageDoubleTapped;
|
||||
|
||||
@override
|
||||
State<StaggeredGridBuilder> createState() => _StaggeredGridBuilderState();
|
||||
}
|
||||
|
||||
class _StaggeredGridBuilderState extends State<StaggeredGridBuilder> {
|
||||
late final UserProfilePB? _userProfile;
|
||||
final List<List<ImageBlockData>> _splitImages = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_userProfile = context.read<DocumentBloc>().state.userProfilePB;
|
||||
|
||||
for (int i = 0; i < widget.images.length; i += 4) {
|
||||
final end = (i + 4 < widget.images.length) ? i + 4 : widget.images.length;
|
||||
_splitImages.add(widget.images.sublist(i, end));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant StaggeredGridBuilder oldWidget) {
|
||||
if (widget.images.length != oldWidget.images.length) {
|
||||
_splitImages.clear();
|
||||
for (int i = 0; i < widget.images.length; i += 4) {
|
||||
final end =
|
||||
(i + 4 < widget.images.length) ? i + 4 : widget.images.length;
|
||||
_splitImages.add(widget.images.sublist(i, end));
|
||||
}
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StaggeredGrid.count(
|
||||
crossAxisCount: 4,
|
||||
mainAxisSpacing: 6,
|
||||
crossAxisSpacing: 6,
|
||||
children:
|
||||
_splitImages.indexed.map(_buildTilesForImages).flattened.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildTilesForImages((int, List<ImageBlockData>) data) {
|
||||
final index = data.$1;
|
||||
final images = data.$2;
|
||||
|
||||
final isReversed = index.isOdd;
|
||||
|
||||
if (images.length == 4) {
|
||||
return [
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: isReversed ? 1 : 2,
|
||||
mainAxisCellCount: isReversed ? 1 : 2,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[0],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 1,
|
||||
mainAxisCellCount: 1,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4 + 1;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[1],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: isReversed ? 2 : 1,
|
||||
mainAxisCellCount: isReversed ? 2 : 1,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4 + 2;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[2],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 2,
|
||||
mainAxisCellCount: 1,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4 + 3;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[3],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (images.length == 3) {
|
||||
return [
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 2,
|
||||
mainAxisCellCount: isReversed ? 1 : 2,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[0],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 2,
|
||||
mainAxisCellCount: isReversed ? 2 : 1,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4 + 1;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[1],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 2,
|
||||
mainAxisCellCount: 1,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4 + 2;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[2],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
} else if (images.length == 2) {
|
||||
return [
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 2,
|
||||
mainAxisCellCount: 2,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[0],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 2,
|
||||
mainAxisCellCount: 2,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4 + 1;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[1],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
StaggeredGridTile.count(
|
||||
crossAxisCellCount: 4,
|
||||
mainAxisCellCount: 2,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () {
|
||||
final imageIndex = index * 4;
|
||||
widget.onImageDoubleTapped(imageIndex);
|
||||
},
|
||||
child: ImageRender(
|
||||
image: images[0],
|
||||
userProfile: _userProfile,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_grid_layout.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide ResizableImage;
|
||||
|
||||
abstract class ImageBlockMultiLayout extends StatefulWidget {
|
||||
const ImageBlockMultiLayout({
|
||||
super.key,
|
||||
required this.node,
|
||||
required this.editorState,
|
||||
required this.images,
|
||||
required this.indexNotifier,
|
||||
required this.isLocalMode,
|
||||
});
|
||||
|
||||
final Node node;
|
||||
final EditorState editorState;
|
||||
final List<ImageBlockData> images;
|
||||
final ValueNotifier<int> indexNotifier;
|
||||
final bool isLocalMode;
|
||||
}
|
||||
|
||||
class ImageLayoutRender extends StatelessWidget {
|
||||
const ImageLayoutRender({
|
||||
super.key,
|
||||
required this.node,
|
||||
required this.editorState,
|
||||
required this.images,
|
||||
required this.indexNotifier,
|
||||
required this.isLocalMode,
|
||||
required this.onIndexChanged,
|
||||
});
|
||||
|
||||
final Node node;
|
||||
final EditorState editorState;
|
||||
final List<ImageBlockData> images;
|
||||
final ValueNotifier<int> indexNotifier;
|
||||
final bool isLocalMode;
|
||||
final void Function(int) onIndexChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final layout = _getLayout();
|
||||
|
||||
return _buildLayout(layout);
|
||||
}
|
||||
|
||||
MultiImageLayout _getLayout() {
|
||||
return MultiImageLayout.fromIntValue(
|
||||
node.attributes[MultiImageBlockKeys.layout] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLayout(MultiImageLayout layout) {
|
||||
switch (layout) {
|
||||
case MultiImageLayout.grid:
|
||||
return ImageGridLayout(
|
||||
node: node,
|
||||
editorState: editorState,
|
||||
images: images,
|
||||
indexNotifier: indexNotifier,
|
||||
isLocalMode: isLocalMode,
|
||||
);
|
||||
case MultiImageLayout.browser:
|
||||
default:
|
||||
return ImageBrowserLayout(
|
||||
node: node,
|
||||
editorState: editorState,
|
||||
images: images,
|
||||
indexNotifier: indexNotifier,
|
||||
isLocalMode: isLocalMode,
|
||||
onIndexChanged: onIndexChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
-8
@@ -1,11 +1,14 @@
|
||||
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/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/multi_image_layouts.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_layouts.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
const kMultiImagePlaceholderKey = 'multiImagePlaceholderKey';
|
||||
@@ -109,6 +112,38 @@ class MultiImageBlockComponentState extends State<MultiImageBlockComponent>
|
||||
|
||||
bool alwaysShowMenu = false;
|
||||
|
||||
static const _interceptorKey = 'multi-image-block-interceptor';
|
||||
|
||||
late final interceptor = SelectionGestureInterceptor(
|
||||
key: _interceptorKey,
|
||||
canTap: (details) => _isTapInBounds(details.globalPosition),
|
||||
canPanStart: (details) => _isTapInBounds(details.globalPosition),
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
editorState.selectionService.registerGestureInterceptor(interceptor);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
editorState.selectionService.unregisterGestureInterceptor(_interceptorKey);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool _isTapInBounds(Offset offset) {
|
||||
if (_renderBox == null) {
|
||||
// We shouldn't block any actions if the render box is not available.
|
||||
// This has the potential to break taps on the editor completely if we
|
||||
// accidentally return false here.
|
||||
return true;
|
||||
}
|
||||
|
||||
final localPosition = _renderBox!.globalToLocal(offset);
|
||||
return !_renderBox!.paintBounds.contains(localPosition);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final data = MultiImageData.fromJson(
|
||||
@@ -127,7 +162,7 @@ class MultiImageBlockComponentState extends State<MultiImageBlockComponent>
|
||||
node: node,
|
||||
);
|
||||
} else {
|
||||
child = ImageBrowserLayout(
|
||||
child = ImageLayoutRender(
|
||||
node: node,
|
||||
images: data.images,
|
||||
editorState: editorState,
|
||||
@@ -302,17 +337,14 @@ class MultiImageData {
|
||||
|
||||
enum MultiImageLayout {
|
||||
browser,
|
||||
masonry,
|
||||
grid;
|
||||
|
||||
int toIntValue() {
|
||||
switch (this) {
|
||||
case MultiImageLayout.browser:
|
||||
return 0;
|
||||
case MultiImageLayout.masonry:
|
||||
return 1;
|
||||
case MultiImageLayout.grid:
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,11 +353,19 @@ enum MultiImageLayout {
|
||||
case 0:
|
||||
return MultiImageLayout.browser;
|
||||
case 1:
|
||||
return MultiImageLayout.masonry;
|
||||
case 2:
|
||||
return MultiImageLayout.grid;
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
String get label => switch (this) {
|
||||
browser => LocaleKeys.document_plugins_photoGallery_browserLayout.tr(),
|
||||
grid => LocaleKeys.document_plugins_photoGallery_gridLayout.tr(),
|
||||
};
|
||||
|
||||
FlowySvgData get icon => switch (this) {
|
||||
browser => FlowySvgs.photo_layout_browser_s,
|
||||
grid => FlowySvgs.photo_layout_grid_s,
|
||||
};
|
||||
}
|
||||
+121
-15
@@ -21,6 +21,8 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu, Log;
|
||||
import 'package:appflowy_popover/appflowy_popover.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/uuid.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:http/http.dart';
|
||||
@@ -57,6 +59,7 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
);
|
||||
|
||||
final PopoverController controller = PopoverController();
|
||||
final PopoverController layoutController = PopoverController();
|
||||
late List<ImageBlockData> images;
|
||||
late final EditorState editorState;
|
||||
|
||||
@@ -73,6 +76,7 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
void dispose() {
|
||||
allowMenuClose();
|
||||
controller.close();
|
||||
layoutController.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -88,6 +92,9 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final layout = MultiImageLayout.fromIntValue(
|
||||
widget.node.attributes[MultiImageBlockKeys.layout] ?? 0,
|
||||
);
|
||||
return Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
@@ -104,11 +111,6 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(4),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.document_imageBlock_openFullScreen.tr(),
|
||||
iconData: FlowySvgs.full_view_s,
|
||||
onTap: openFullScreen,
|
||||
),
|
||||
AppFlowyPopover(
|
||||
controller: controller,
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
@@ -141,9 +143,57 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
const HSpace(4),
|
||||
AppFlowyPopover(
|
||||
controller: layoutController,
|
||||
onClose: allowMenuClose,
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
offset: const Offset(0, 10),
|
||||
constraints: const BoxConstraints(
|
||||
maxHeight: 300,
|
||||
maxWidth: 300,
|
||||
),
|
||||
popupBuilder: (context) {
|
||||
preventMenuClose();
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_LayoutSelector(
|
||||
selectedLayout: layout,
|
||||
onSelected: (layout) {
|
||||
allowMenuClose();
|
||||
layoutController.close();
|
||||
final transaction = editorState.transaction;
|
||||
transaction.updateNode(widget.node, {
|
||||
MultiImageBlockKeys.images:
|
||||
widget.node.attributes[MultiImageBlockKeys.images],
|
||||
MultiImageBlockKeys.layout: layout.toIntValue(),
|
||||
});
|
||||
editorState.apply(transaction);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: MenuBlockButton(
|
||||
tooltip: LocaleKeys
|
||||
.document_plugins_photoGallery_changeLayoutTooltip
|
||||
.tr(),
|
||||
iconData: FlowySvgs.edit_layout_s,
|
||||
onTap: () {},
|
||||
),
|
||||
),
|
||||
const HSpace(4),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.document_imageBlock_openFullScreen.tr(),
|
||||
iconData: FlowySvgs.full_view_s,
|
||||
onTap: openFullScreen,
|
||||
),
|
||||
|
||||
// disable the copy link button if the image is hosted on appflowy cloud
|
||||
// because the url needs the verification token to be accessible
|
||||
if (!images[widget.indexNotifier.value].url.isAppFlowyCloudUrl) ...[
|
||||
if (layout == MultiImageLayout.browser &&
|
||||
!images[widget.indexNotifier.value].url.isAppFlowyCloudUrl) ...[
|
||||
const HSpace(4),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.editor_copyLink.tr(),
|
||||
@@ -153,7 +203,8 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
],
|
||||
const _Divider(),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.button_delete.tr(),
|
||||
tooltip: LocaleKeys.document_plugins_photoGallery_deleteBlockTooltip
|
||||
.tr(),
|
||||
iconData: FlowySvgs.delete_s,
|
||||
onTap: deleteImage,
|
||||
),
|
||||
@@ -202,8 +253,8 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
newImages.map((image) => image.toJson()).toList();
|
||||
transaction.updateNode(widget.node, {
|
||||
MultiImageBlockKeys.images: imagesJson,
|
||||
// Default to Browser layout
|
||||
MultiImageBlockKeys.layout: MultiImageLayout.browser.toIntValue(),
|
||||
MultiImageBlockKeys.layout:
|
||||
widget.node.attributes[MultiImageBlockKeys.layout],
|
||||
});
|
||||
|
||||
await editorState.apply(transaction);
|
||||
@@ -242,12 +293,12 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
return;
|
||||
}
|
||||
|
||||
newImages.addAll(images);
|
||||
final imagesJson = newImages.map((image) => image.toJson()).toList();
|
||||
final imagesJson =
|
||||
[...images, ...newImages].map((i) => i.toJson()).toList();
|
||||
transaction.updateNode(widget.node, {
|
||||
MultiImageBlockKeys.images: imagesJson,
|
||||
// Default to Browser layout
|
||||
MultiImageBlockKeys.layout: MultiImageLayout.browser.toIntValue(),
|
||||
MultiImageBlockKeys.layout:
|
||||
widget.node.attributes[MultiImageBlockKeys.layout],
|
||||
});
|
||||
|
||||
await editorState.apply(transaction);
|
||||
@@ -310,8 +361,8 @@ class _MultiImageMenuState extends State<MultiImageMenu> {
|
||||
final imagesJson = newImages.map((image) => image.toJson()).toList();
|
||||
transaction.updateNode(widget.node, {
|
||||
MultiImageBlockKeys.images: imagesJson,
|
||||
// Default to Browser layout
|
||||
MultiImageBlockKeys.layout: MultiImageLayout.browser.toIntValue(),
|
||||
MultiImageBlockKeys.layout:
|
||||
widget.node.attributes[MultiImageBlockKeys.layout],
|
||||
});
|
||||
|
||||
await editorState.apply(transaction);
|
||||
@@ -330,3 +381,58 @@ class _Divider extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LayoutSelector extends StatelessWidget {
|
||||
const _LayoutSelector({
|
||||
required this.selectedLayout,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
final MultiImageLayout selectedLayout;
|
||||
final Function(MultiImageLayout) onSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SeparatedRow(
|
||||
separatorBuilder: () => const HSpace(6),
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: MultiImageLayout.values
|
||||
.map(
|
||||
(layout) => MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () => onSelected(layout),
|
||||
child: Container(
|
||||
height: 80,
|
||||
width: 80,
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2,
|
||||
color: selectedLayout == layout
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).dividerColor,
|
||||
),
|
||||
borderRadius: Corners.s8Border,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FlowySvg(
|
||||
layout.icon,
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
size: const Size.square(24),
|
||||
),
|
||||
const VSpace(6),
|
||||
FlowyText(layout.label),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -222,8 +222,9 @@ class MultiImagePlaceholderState extends State<MultiImagePlaceholder> {
|
||||
|
||||
transaction.updateNode(widget.node, {
|
||||
MultiImageBlockKeys.images: imagesJson,
|
||||
// Default to Browser layout
|
||||
MultiImageBlockKeys.layout: MultiImageLayout.browser.toIntValue(),
|
||||
MultiImageBlockKeys.layout:
|
||||
widget.node.attributes[MultiImageBlockKeys.layout] ??
|
||||
MultiImageLayout.browser.toIntValue(),
|
||||
});
|
||||
|
||||
await editorState.apply(transaction);
|
||||
@@ -282,8 +283,9 @@ class MultiImagePlaceholderState extends State<MultiImagePlaceholder> {
|
||||
transaction.updateNode(widget.node, {
|
||||
MultiImageBlockKeys.images:
|
||||
images.map((image) => image.toJson()).toList(),
|
||||
// Default to Browser layout
|
||||
MultiImageBlockKeys.layout: MultiImageLayout.browser.toIntValue(),
|
||||
MultiImageBlockKeys.layout:
|
||||
widget.node.attributes[MultiImageBlockKeys.layout] ??
|
||||
MultiImageLayout.browser.toIntValue(),
|
||||
});
|
||||
await editorState.apply(transaction);
|
||||
}
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ class FontFamilyItem extends StatelessWidget {
|
||||
}
|
||||
});
|
||||
},
|
||||
text: (fontFamily ?? systemFonFamily).parseFontFamilyName(),
|
||||
text: (fontFamily ?? systemFonFamily).fontFamilyDisplayName,
|
||||
fontFamily: fontFamily ?? systemFonFamily,
|
||||
backgroundColor: theme.toolbarMenuItemBackgroundColor,
|
||||
isSelected: false,
|
||||
|
||||
+2
-2
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/build_context_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/text_robot.dart';
|
||||
@@ -13,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'ai_limit_dialog.dart';
|
||||
@@ -178,6 +177,7 @@ class _AutoCompletionBlockComponentState
|
||||
await editorState.apply(
|
||||
transaction,
|
||||
options: const ApplyOptions(recordUndo: false),
|
||||
withUpdateSelection: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -49,7 +49,7 @@ extension _StartWithsSort on List<InlineActionsResult> {
|
||||
);
|
||||
}
|
||||
|
||||
const _invalidSearchesAmount = 20;
|
||||
const _invalidSearchesAmount = 10;
|
||||
|
||||
class InlineActionsHandler extends StatefulWidget {
|
||||
const InlineActionsHandler({
|
||||
@@ -81,8 +81,6 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
||||
final _focusNode = FocusNode(debugLabel: 'inline_actions_menu_handler');
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
Timer? _debounce;
|
||||
|
||||
late List<InlineActionsResult> results = widget.results;
|
||||
int invalidCounter = 0;
|
||||
late int startOffset;
|
||||
@@ -90,8 +88,7 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
||||
String _search = '';
|
||||
set search(String search) {
|
||||
_search = search;
|
||||
_debounce?.cancel();
|
||||
_debounce = Timer(const Duration(milliseconds: 200), _doSearch);
|
||||
_doSearch();
|
||||
}
|
||||
|
||||
Future<void> _doSearch() async {
|
||||
@@ -109,10 +106,13 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
||||
: 0;
|
||||
|
||||
if (invalidCounter >= _invalidSearchesAmount) {
|
||||
widget.onDismiss();
|
||||
|
||||
// Workaround to bring focus back to editor
|
||||
await widget.editorState
|
||||
.updateSelectionWithReason(widget.editorState.selection);
|
||||
return widget.onDismiss();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_resetSelection();
|
||||
@@ -143,7 +143,6 @@ class _InlineActionsHandlerState extends State<InlineActionsHandler> {
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_focusNode.dispose();
|
||||
_debounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
typedef FeatureFlagMap = Map<FeatureFlag, bool>;
|
||||
|
||||
|
||||
@@ -208,11 +208,6 @@ void _resolveFolderDeps(GetIt getIt) {
|
||||
),
|
||||
);
|
||||
|
||||
// Settings
|
||||
getIt.registerFactoryParam<SettingsDialogBloc, UserProfilePB, void>(
|
||||
(user, _) => SettingsDialogBloc(user),
|
||||
);
|
||||
|
||||
// User
|
||||
getIt.registerFactoryParam<SettingsUserViewBloc, UserProfilePB, void>(
|
||||
(user, _) => SettingsUserViewBloc(user),
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
@@ -24,8 +21,11 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme.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 'package:go_router/go_router.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
import 'prelude.dart';
|
||||
|
||||
@@ -197,31 +197,33 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
|
||||
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
||||
builder: (context, state) {
|
||||
_setSystemOverlayStyle(state);
|
||||
return MaterialApp.router(
|
||||
builder: (context, child) => MediaQuery(
|
||||
// use the 1.0 as the textScaleFactor to avoid the text size
|
||||
// affected by the system setting.
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(state.textScaleFactor),
|
||||
),
|
||||
child: overlayManagerBuilder(
|
||||
context,
|
||||
!PlatformExtension.isMobile && FeatureFlag.search.isOn
|
||||
? CommandPalette(
|
||||
notifier: _commandPaletteNotifier,
|
||||
child: child,
|
||||
)
|
||||
: child,
|
||||
return ToastificationWrapper(
|
||||
child: MaterialApp.router(
|
||||
builder: (context, child) => MediaQuery(
|
||||
// use the 1.0 as the textScaleFactor to avoid the text size
|
||||
// affected by the system setting.
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(state.textScaleFactor),
|
||||
),
|
||||
child: overlayManagerBuilder(
|
||||
context,
|
||||
!PlatformExtension.isMobile && FeatureFlag.search.isOn
|
||||
? CommandPalette(
|
||||
notifier: _commandPaletteNotifier,
|
||||
child: child,
|
||||
)
|
||||
: child,
|
||||
),
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: state.lightTheme,
|
||||
darkTheme: state.darkTheme,
|
||||
themeMode: state.themeMode,
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: state.locale,
|
||||
routerConfig: routerConfig,
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: state.lightTheme,
|
||||
darkTheme: state.darkTheme,
|
||||
themeMode: state.themeMode,
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: state.locale,
|
||||
routerConfig: routerConfig,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' show launchUrl;
|
||||
part 'download_offline_ai_app_bloc.freezed.dart';
|
||||
|
||||
class DownloadOfflineAIBloc
|
||||
extends Bloc<DownloadOfflineAIEvent, DownloadOfflineAIState> {
|
||||
DownloadOfflineAIBloc() : super(const DownloadOfflineAIState()) {
|
||||
on<DownloadOfflineAIEvent>(_handleEvent);
|
||||
}
|
||||
|
||||
Future<void> _handleEvent(
|
||||
DownloadOfflineAIEvent event,
|
||||
Emitter<DownloadOfflineAIState> emit,
|
||||
) async {
|
||||
await event.when(
|
||||
started: () async {
|
||||
final result = await ChatEventGetOfflineAIAppLink().send();
|
||||
await result.fold(
|
||||
(app) async {
|
||||
await launchUrl(Uri.parse(app.link));
|
||||
},
|
||||
(err) {},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DownloadOfflineAIEvent with _$DownloadOfflineAIEvent {
|
||||
const factory DownloadOfflineAIEvent.started() = _Started;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DownloadOfflineAIState with _$DownloadOfflineAIState {
|
||||
const factory DownloadOfflineAIState() = _DownloadOfflineAIState;
|
||||
}
|
||||
+65
-39
@@ -20,7 +20,7 @@ class LocalAIChatSettingBloc
|
||||
listener.start(
|
||||
stateCallback: (newState) {
|
||||
if (!isClosed) {
|
||||
add(LocalAIChatSettingEvent.updateLLMRunningState(newState.state));
|
||||
add(LocalAIChatSettingEvent.updatePluginState(newState));
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -46,14 +46,14 @@ class LocalAIChatSettingBloc
|
||||
modelInfo: modelInfo,
|
||||
models: modelInfo.models,
|
||||
selectedLLMModel: modelInfo.selectedModel,
|
||||
fetchModelInfoState: const LoadingState.finish(),
|
||||
aiModelProgress: const AIModelProgress.finish(),
|
||||
),
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
fetchModelInfoState: LoadingState.finish(error: err),
|
||||
aiModelProgress: AIModelProgress.finish(error: err),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -68,7 +68,7 @@ class LocalAIChatSettingBloc
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedLLMModel: llmModel,
|
||||
localAIInfo: LocalAIProgress.showDownload(
|
||||
progressIndicator: LocalAIProgress.showDownload(
|
||||
llmResource,
|
||||
llmModel,
|
||||
),
|
||||
@@ -80,7 +80,7 @@ class LocalAIChatSettingBloc
|
||||
state.copyWith(
|
||||
selectedLLMModel: llmModel,
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
localAIInfo: const LocalAIProgress.checkPluginState(),
|
||||
progressIndicator: const LocalAIProgress.checkPluginState(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ class LocalAIChatSettingBloc
|
||||
if (llmResource.pendingResources.isEmpty) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo: const LocalAIProgress.checkPluginState(),
|
||||
progressIndicator: const LocalAIProgress.checkPluginState(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -115,7 +115,7 @@ class LocalAIChatSettingBloc
|
||||
if (llmResource.isDownloading) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo:
|
||||
progressIndicator:
|
||||
LocalAIProgress.startDownloading(state.selectedLLMModel!),
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
),
|
||||
@@ -124,7 +124,7 @@ class LocalAIChatSettingBloc
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo: LocalAIProgress.showDownload(
|
||||
progressIndicator: LocalAIProgress.showDownload(
|
||||
llmResource,
|
||||
state.selectedLLMModel!,
|
||||
),
|
||||
@@ -138,7 +138,7 @@ class LocalAIChatSettingBloc
|
||||
startDownloadModel: (LLMModelPB llmModel) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
localAIInfo: LocalAIProgress.startDownloading(llmModel),
|
||||
progressIndicator: LocalAIProgress.startDownloading(llmModel),
|
||||
selectLLMState: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
@@ -149,19 +149,39 @@ class LocalAIChatSettingBloc
|
||||
},
|
||||
finishDownload: () async {
|
||||
emit(
|
||||
state.copyWith(localAIInfo: const LocalAIProgress.finishDownload()),
|
||||
state.copyWith(
|
||||
progressIndicator: const LocalAIProgress.finishDownload(),
|
||||
),
|
||||
);
|
||||
},
|
||||
updateLLMRunningState: (RunningStatePB newRunningState) {
|
||||
if (newRunningState == RunningStatePB.Stopped) {
|
||||
updatePluginState: (LocalAIPluginStatePB pluginState) {
|
||||
if (pluginState.offlineAiReady) {
|
||||
ChatEventRefreshLocalAIModelInfo().send().then((result) {
|
||||
if (!isClosed) {
|
||||
add(LocalAIChatSettingEvent.didLoadModelInfo(result));
|
||||
}
|
||||
});
|
||||
|
||||
if (pluginState.state == RunningStatePB.Stopped) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
runningState: pluginState.state,
|
||||
progressIndicator: const LocalAIProgress.checkPluginState(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
runningState: pluginState.state,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
emit(
|
||||
state.copyWith(
|
||||
runningState: newRunningState,
|
||||
localAIInfo: const LocalAIProgress.checkPluginState(),
|
||||
progressIndicator: const LocalAIProgress.startOfflineAIApp(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(state.copyWith(runningState: newRunningState));
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -183,10 +203,21 @@ class LocalAIChatSettingBloc
|
||||
|
||||
/// Handles the event to fetch local AI settings when the application starts.
|
||||
Future<void> _handleStarted() async {
|
||||
final result = await ChatEventRefreshLocalAIModelInfo().send();
|
||||
if (!isClosed) {
|
||||
add(LocalAIChatSettingEvent.didLoadModelInfo(result));
|
||||
}
|
||||
final result = await ChatEventGetLocalAIPluginState().send();
|
||||
result.fold(
|
||||
(pluginState) async {
|
||||
if (!isClosed) {
|
||||
add(LocalAIChatSettingEvent.updatePluginState(pluginState));
|
||||
if (pluginState.offlineAiReady) {
|
||||
final result = await ChatEventRefreshLocalAIModelInfo().send();
|
||||
if (!isClosed) {
|
||||
add(LocalAIChatSettingEvent.didLoadModelInfo(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err.toString()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -214,9 +245,9 @@ class LocalAIChatSettingEvent with _$LocalAIChatSettingEvent {
|
||||
|
||||
const factory LocalAIChatSettingEvent.cancelDownload() = _CancelDownload;
|
||||
const factory LocalAIChatSettingEvent.finishDownload() = _FinishDownload;
|
||||
const factory LocalAIChatSettingEvent.updateLLMRunningState(
|
||||
RunningStatePB newRunningState,
|
||||
) = _RunningState;
|
||||
const factory LocalAIChatSettingEvent.updatePluginState(
|
||||
LocalAIPluginStatePB pluginState,
|
||||
) = _PluginState;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -224,29 +255,16 @@ class LocalAIChatSettingState with _$LocalAIChatSettingState {
|
||||
const factory LocalAIChatSettingState({
|
||||
LLMModelInfoPB? modelInfo,
|
||||
LLMModelPB? selectedLLMModel,
|
||||
LocalAIProgress? localAIInfo,
|
||||
@Default(LoadingState.loading()) LoadingState fetchModelInfoState,
|
||||
LocalAIProgress? progressIndicator,
|
||||
@Default(AIModelProgress.init()) AIModelProgress aiModelProgress,
|
||||
@Default(LoadingState.loading()) LoadingState selectLLMState,
|
||||
@Default([]) List<LLMModelPB> models,
|
||||
@Default(RunningStatePB.Connecting) RunningStatePB runningState,
|
||||
}) = _LocalAIChatSettingState;
|
||||
}
|
||||
|
||||
// @freezed
|
||||
// class LocalChatAIStateIndicator with _$LocalChatAIStateIndicator {
|
||||
// // when start downloading the model
|
||||
// const factory LocalChatAIStateIndicator.error(FlowyError error) = _OnError;
|
||||
// const factory LocalChatAIStateIndicator.ready(bool isEnabled) = _Ready;
|
||||
// }
|
||||
|
||||
@freezed
|
||||
class LocalAIProgress with _$LocalAIProgress {
|
||||
// when user select a new model, it will call requestDownload
|
||||
const factory LocalAIProgress.requestDownloadInfo(
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) = _RequestDownload;
|
||||
|
||||
// when user comes back to the setting page, it will auto detect current llm state
|
||||
const factory LocalAIProgress.showDownload(
|
||||
LocalModelResourcePB llmResource,
|
||||
@@ -257,5 +275,13 @@ class LocalAIProgress with _$LocalAIProgress {
|
||||
const factory LocalAIProgress.startDownloading(LLMModelPB llmModel) =
|
||||
_Downloading;
|
||||
const factory LocalAIProgress.finishDownload() = _Finish;
|
||||
const factory LocalAIProgress.checkPluginState() = _PluginState;
|
||||
const factory LocalAIProgress.checkPluginState() = _CheckPluginState;
|
||||
const factory LocalAIProgress.startOfflineAIApp() = _StartOfflineAIApp;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class AIModelProgress with _$AIModelProgress {
|
||||
const factory AIModelProgress.init() = _AIModelProgressInit;
|
||||
const factory AIModelProgress.loading() = _AIModelDownloading;
|
||||
const factory AIModelProgress.finish({FlowyError? error}) = _AIModelFinish;
|
||||
}
|
||||
+38
-14
@@ -7,6 +7,7 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:url_launcher/url_launcher.dart' show launchUrl;
|
||||
part 'plugin_state_bloc.freezed.dart';
|
||||
|
||||
class PluginStateBloc extends Bloc<PluginStateEvent, PluginStateState> {
|
||||
@@ -53,19 +54,31 @@ class PluginStateBloc extends Bloc<PluginStateEvent, PluginStateState> {
|
||||
);
|
||||
},
|
||||
updateState: (LocalAIPluginStatePB pluginState) {
|
||||
switch (pluginState.state) {
|
||||
case RunningStatePB.Connecting:
|
||||
emit(
|
||||
const PluginStateState(action: PluginStateAction.loadingPlugin()),
|
||||
);
|
||||
case RunningStatePB.Running:
|
||||
emit(const PluginStateState(action: PluginStateAction.ready()));
|
||||
break;
|
||||
default:
|
||||
emit(
|
||||
state.copyWith(action: const PluginStateAction.restart()),
|
||||
);
|
||||
break;
|
||||
// if the offline ai is not started, ask user to start it
|
||||
if (pluginState.offlineAiReady) {
|
||||
// Chech state of the plugin
|
||||
switch (pluginState.state) {
|
||||
case RunningStatePB.Connecting:
|
||||
emit(
|
||||
const PluginStateState(
|
||||
action: PluginStateAction.loadingPlugin(),
|
||||
),
|
||||
);
|
||||
case RunningStatePB.Running:
|
||||
emit(const PluginStateState(action: PluginStateAction.ready()));
|
||||
break;
|
||||
default:
|
||||
emit(
|
||||
state.copyWith(action: const PluginStateAction.restartPlugin()),
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
emit(
|
||||
const PluginStateState(
|
||||
action: PluginStateAction.startAIOfflineApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
restartLocalAI: () async {
|
||||
@@ -83,6 +96,15 @@ class PluginStateBloc extends Bloc<PluginStateEvent, PluginStateState> {
|
||||
(err) => Log.error(err.toString()),
|
||||
);
|
||||
},
|
||||
downloadOfflineAIApp: () async {
|
||||
final result = await ChatEventGetOfflineAIAppLink().send();
|
||||
await result.fold(
|
||||
(app) async {
|
||||
await launchUrl(Uri.parse(app.link));
|
||||
},
|
||||
(err) {},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -95,6 +117,7 @@ class PluginStateEvent with _$PluginStateEvent {
|
||||
const factory PluginStateEvent.restartLocalAI() = _RestartLocalAI;
|
||||
const factory PluginStateEvent.openModelDirectory() =
|
||||
_OpenModelStorageDirectory;
|
||||
const factory PluginStateEvent.downloadOfflineAIApp() = _DownloadOfflineAIApp;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -109,5 +132,6 @@ class PluginStateAction with _$PluginStateAction {
|
||||
const factory PluginStateAction.init() = _Init;
|
||||
const factory PluginStateAction.loadingPlugin() = _LoadingPlugin;
|
||||
const factory PluginStateAction.ready() = _Ready;
|
||||
const factory PluginStateAction.restart() = _Restart;
|
||||
const factory PluginStateAction.restartPlugin() = _RestartPlugin;
|
||||
const factory PluginStateAction.startAIOfflineApp() = _StartAIOfflineApp;
|
||||
}
|
||||
@@ -43,6 +43,9 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
emit(state.copyWith(userProfile: userProfile));
|
||||
},
|
||||
toggleAISearch: () {
|
||||
emit(
|
||||
state.copyWith(enableSearchIndexing: !state.enableSearchIndexing),
|
||||
);
|
||||
_updateUserWorkspaceSetting(
|
||||
disableSearchIndexing:
|
||||
!(state.aiSettings?.disableSearchIndexing ?? false),
|
||||
|
||||
+55
-1
@@ -1,7 +1,12 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -27,7 +32,8 @@ enum SettingsPage {
|
||||
class SettingsDialogBloc
|
||||
extends Bloc<SettingsDialogEvent, SettingsDialogState> {
|
||||
SettingsDialogBloc(
|
||||
this.userProfile, {
|
||||
this.userProfile,
|
||||
this.workspaceMember, {
|
||||
SettingsPage? initPage,
|
||||
}) : _userListener = UserListener(userProfile: userProfile),
|
||||
super(SettingsDialogState.initial(userProfile, initPage)) {
|
||||
@@ -35,6 +41,7 @@ class SettingsDialogBloc
|
||||
}
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB? workspaceMember;
|
||||
final UserListener _userListener;
|
||||
|
||||
@override
|
||||
@@ -49,6 +56,12 @@ class SettingsDialogBloc
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_userListener.start(onProfileUpdated: _profileUpdated);
|
||||
|
||||
final isBillingEnabled =
|
||||
await _isBillingEnabled(userProfile, workspaceMember);
|
||||
if (isBillingEnabled) {
|
||||
emit(state.copyWith(isBillingEnabled: true));
|
||||
}
|
||||
},
|
||||
didReceiveUserProfile: (UserProfilePB newUserProfile) {
|
||||
emit(state.copyWith(userProfile: newUserProfile));
|
||||
@@ -70,6 +83,45 @@ class SettingsDialogBloc
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _isBillingEnabled(
|
||||
UserProfilePB userProfile, [
|
||||
WorkspaceMemberPB? member,
|
||||
]) async {
|
||||
if ([
|
||||
AuthenticatorPB.Local,
|
||||
AuthenticatorPB.Supabase,
|
||||
].contains(userProfile.authenticator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (member == null || member.role != AFRolePB.Owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final result = await UserEventGetCloudConfig().send();
|
||||
return result.fold(
|
||||
(cloudSetting) {
|
||||
final whiteList = [
|
||||
"https://beta.appflowy.cloud",
|
||||
"https://test.appflowy.cloud",
|
||||
];
|
||||
if (kDebugMode) {
|
||||
whiteList.add("http://localhost:8000");
|
||||
}
|
||||
|
||||
return whiteList.contains(cloudSetting.serverUrl);
|
||||
},
|
||||
(err) {
|
||||
Log.error("Failed to get cloud config: $err");
|
||||
return false;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -87,6 +139,7 @@ class SettingsDialogState with _$SettingsDialogState {
|
||||
const factory SettingsDialogState({
|
||||
required UserProfilePB userProfile,
|
||||
required SettingsPage page,
|
||||
required bool isBillingEnabled,
|
||||
}) = _SettingsDialogState;
|
||||
|
||||
factory SettingsDialogState.initial(
|
||||
@@ -96,5 +149,6 @@ class SettingsDialogState with _$SettingsDialogState {
|
||||
SettingsDialogState(
|
||||
userProfile: userProfile,
|
||||
page: page ?? SettingsPage.account,
|
||||
isBillingEnabled: false,
|
||||
);
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class ViewExtKeys {
|
||||
}
|
||||
|
||||
extension ViewExtension on ViewPB {
|
||||
Widget defaultIcon() => FlowySvg(
|
||||
Widget defaultIcon({Size? size}) => FlowySvg(
|
||||
switch (layout) {
|
||||
ViewLayoutPB.Board => FlowySvgs.icon_board_s,
|
||||
ViewLayoutPB.Calendar => FlowySvgs.icon_calendar_s,
|
||||
@@ -57,6 +57,7 @@ extension ViewExtension on ViewPB {
|
||||
ViewLayoutPB.Chat => FlowySvgs.chat_ai_page_s,
|
||||
_ => FlowySvgs.document_s,
|
||||
},
|
||||
size: size,
|
||||
);
|
||||
|
||||
PluginType get pluginType => switch (layout) {
|
||||
|
||||
+7
-3
@@ -150,12 +150,16 @@ class _FavoriteGroups extends StatelessWidget {
|
||||
children: [
|
||||
if (today.isNotEmpty) ...[
|
||||
...today,
|
||||
const FlowyDivider(),
|
||||
const VSpace(16),
|
||||
],
|
||||
if (thisWeek.isNotEmpty) ...[
|
||||
if (today.isNotEmpty) ...[
|
||||
const FlowyDivider(),
|
||||
const VSpace(16),
|
||||
],
|
||||
...thisWeek,
|
||||
const VSpace(8),
|
||||
],
|
||||
if ((thisWeek.isNotEmpty || today.isNotEmpty) &&
|
||||
others.isNotEmpty) ...[
|
||||
const FlowyDivider(),
|
||||
const VSpace(16),
|
||||
],
|
||||
|
||||
+2
-2
@@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
@@ -14,6 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class FavoriteMoreActions extends StatelessWidget {
|
||||
@@ -28,6 +27,7 @@ class FavoriteMoreActions extends StatelessWidget {
|
||||
child: ViewMoreActionButton(
|
||||
view: view,
|
||||
spaceType: FolderSpaceType.favorite,
|
||||
isExpanded: false,
|
||||
onEditing: (value) =>
|
||||
context.read<ViewBloc>().add(ViewEvent.setIsEditing(value)),
|
||||
onAction: (action, _) {
|
||||
|
||||
+13
-10
@@ -275,7 +275,6 @@ class _SidebarState extends State<_Sidebar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const menuHorizontalInset = EdgeInsets.symmetric(horizontal: 8);
|
||||
final userState = context.read<UserWorkspaceBloc>().state;
|
||||
return MouseRegion(
|
||||
onEnter: (_) => _isHovered.value = true,
|
||||
onExit: (_) => _isHovered.value = false,
|
||||
@@ -297,15 +296,19 @@ class _SidebarState extends State<_Sidebar> {
|
||||
),
|
||||
),
|
||||
// user or workspace, setting
|
||||
Container(
|
||||
height: HomeSizes.workspaceSectionHeight,
|
||||
padding: menuHorizontalInset - const EdgeInsets.only(right: 6),
|
||||
child:
|
||||
// if the workspaces are empty, show the user profile instead
|
||||
userState.isCollabWorkspaceOn &&
|
||||
userState.workspaces.isNotEmpty
|
||||
? SidebarWorkspace(userProfile: widget.userProfile)
|
||||
: SidebarUser(userProfile: widget.userProfile),
|
||||
BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
height: HomeSizes.workspaceSectionHeight,
|
||||
padding:
|
||||
menuHorizontalInset - const EdgeInsets.only(right: 6),
|
||||
child:
|
||||
// if the workspaces are empty, show the user profile instead
|
||||
state.isCollabWorkspaceOn && state.workspaces.isNotEmpty
|
||||
? SidebarWorkspace(userProfile: widget.userProfile)
|
||||
: SidebarUser(userProfile: widget.userProfile),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (FeatureFlag.search.isOn) ...[
|
||||
const VSpace(6),
|
||||
|
||||
+6
-1
@@ -292,6 +292,7 @@ class ConfirmPopup extends StatefulWidget {
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.onConfirm,
|
||||
this.onCancel,
|
||||
this.confirmLabel,
|
||||
this.confirmButtonColor,
|
||||
});
|
||||
@@ -299,6 +300,7 @@ class ConfirmPopup extends StatefulWidget {
|
||||
final String title;
|
||||
final String description;
|
||||
final VoidCallback onConfirm;
|
||||
final VoidCallback? onCancel;
|
||||
final Color? confirmButtonColor;
|
||||
final ConfirmPopupStyle style;
|
||||
|
||||
@@ -392,7 +394,10 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
|
||||
);
|
||||
case ConfirmPopupStyle.cancelAndOk:
|
||||
return SpaceCancelOrConfirmButton(
|
||||
onCancel: () => Navigator.of(context).pop(),
|
||||
onCancel: () {
|
||||
widget.onCancel?.call();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
onConfirm: () {
|
||||
widget.onConfirm();
|
||||
Navigator.of(context).pop();
|
||||
|
||||
+7
-2
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
@@ -198,9 +199,9 @@ class _SidebarSwitchWorkspaceButtonState
|
||||
),
|
||||
);
|
||||
},
|
||||
child: FlowyButton(
|
||||
child: FlowyIconTextButton(
|
||||
margin: EdgeInsets.zero,
|
||||
text: SizedBox(
|
||||
textBuilder: (onHover) => SizedBox(
|
||||
height: 30,
|
||||
child: Row(
|
||||
children: [
|
||||
@@ -229,6 +230,10 @@ class _SidebarSwitchWorkspaceButtonState
|
||||
),
|
||||
),
|
||||
const HSpace(4),
|
||||
if (onHover)
|
||||
const FlowySvg(
|
||||
FlowySvgs.workspace_drop_down_menu_show_s,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -693,6 +693,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
message: LocaleKeys.menuAppHeader_moreButtonToolTip.tr(),
|
||||
child: ViewMoreActionButton(
|
||||
view: widget.view,
|
||||
isExpanded: widget.isExpanded,
|
||||
spaceType: widget.spaceType,
|
||||
onEditing: (value) =>
|
||||
context.read<ViewBloc>().add(ViewEvent.setIsEditing(value)),
|
||||
|
||||
+5
-1
@@ -19,12 +19,14 @@ class ViewMoreActionButton extends StatelessWidget {
|
||||
required this.onEditing,
|
||||
required this.onAction,
|
||||
required this.spaceType,
|
||||
required this.isExpanded,
|
||||
});
|
||||
|
||||
final ViewPB view;
|
||||
final void Function(bool value) onEditing;
|
||||
final void Function(ViewMoreActionType type, dynamic data) onAction;
|
||||
final FolderSpaceType spaceType;
|
||||
final bool isExpanded;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -102,7 +104,9 @@ class ViewMoreActionButton extends StatelessWidget {
|
||||
|
||||
// Chat doesn't change collapse
|
||||
// Only show collapse all pages if the view has child views
|
||||
if (view.layout != ViewLayoutPB.Chat && view.childViews.isNotEmpty) {
|
||||
if (view.layout != ViewLayoutPB.Chat &&
|
||||
view.childViews.isNotEmpty &&
|
||||
isExpanded) {
|
||||
actionTypes.add(ViewMoreActionType.collapseAllPages);
|
||||
actionTypes.add(ViewMoreActionType.divider);
|
||||
}
|
||||
|
||||
+17
-14
@@ -38,20 +38,23 @@ class InitLocalAIIndicator extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
case RunningStatePB.Running:
|
||||
return Row(
|
||||
children: [
|
||||
const HSpace(8),
|
||||
const FlowySvg(
|
||||
FlowySvgs.download_success_s,
|
||||
color: Color(0xFF2E7D32),
|
||||
),
|
||||
const HSpace(6),
|
||||
FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_localAILoaded.tr(),
|
||||
fontSize: 11,
|
||||
color: const Color(0xFF1E4620),
|
||||
),
|
||||
],
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(8),
|
||||
const FlowySvg(
|
||||
FlowySvgs.download_success_s,
|
||||
color: Color(0xFF2E7D32),
|
||||
),
|
||||
const HSpace(6),
|
||||
FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_localAILoaded.tr(),
|
||||
fontSize: 11,
|
||||
color: const Color(0xFF1E4620),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
case RunningStatePB.Stopped:
|
||||
return Row(
|
||||
|
||||
+89
-134
@@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_ai_chat_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_ai_chat_toggle_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/init_local_ai.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/plugin_state.dart';
|
||||
@@ -67,53 +68,53 @@ class LocalAIChatSetting extends StatelessWidget {
|
||||
tapBodyToExpand: false,
|
||||
tapHeaderToExpand: false,
|
||||
),
|
||||
header: const LocalAIChatSettingHeader(),
|
||||
header: const SizedBox.shrink(),
|
||||
collapsed: const SizedBox.shrink(),
|
||||
expanded: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
// child: _LocalLLMInfoWidget(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_aiPage_keys_llmModel.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
BlocBuilder<LocalAIChatSettingBloc,
|
||||
LocalAIChatSettingState>(
|
||||
builder: (context, state) {
|
||||
return state.fetchModelInfoState.when(
|
||||
loading: () => Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText(
|
||||
LocaleKeys
|
||||
.settings_aiPage_keys_fetchLocalModel
|
||||
.tr(),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const CircularProgressIndicator.adaptive(),
|
||||
],
|
||||
BlocBuilder<LocalAIChatSettingBloc, LocalAIChatSettingState>(
|
||||
builder: (context, state) {
|
||||
// If the progress indicator is startOfflineAIApp, then don't show the LLM model.
|
||||
if (state.progressIndicator ==
|
||||
const LocalAIProgress.startOfflineAIApp()) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_aiPage_keys_llmModel.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
finish: (err) {
|
||||
return (err == null)
|
||||
const Spacer(),
|
||||
state.aiModelProgress.when(
|
||||
init: () => const SizedBox.shrink(),
|
||||
loading: () {
|
||||
return const Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Spacer(),
|
||||
CircularProgressIndicator.adaptive(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
finish: (err) => (err == null)
|
||||
? const _SelectLocalModelDropdownMenu()
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const IntrinsicHeight(child: _LocalLLMInfoWidget()),
|
||||
const IntrinsicHeight(child: _LocalAIStateWidget()),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -200,8 +201,8 @@ class _SelectLocalModelDropdownMenu extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _LocalLLMInfoWidget extends StatelessWidget {
|
||||
const _LocalLLMInfoWidget();
|
||||
class _LocalAIStateWidget extends StatelessWidget {
|
||||
const _LocalAIStateWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -210,15 +211,8 @@ class _LocalLLMInfoWidget extends StatelessWidget {
|
||||
final error = errorFromState(state);
|
||||
if (error == null) {
|
||||
// If the error is null, handle selected llm model.
|
||||
if (state.localAIInfo != null) {
|
||||
final child = state.localAIInfo!.when(
|
||||
requestDownloadInfo: (
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) {
|
||||
_showDownloadDialog(context, llmResource, llmModel);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
if (state.progressIndicator != null) {
|
||||
final child = state.progressIndicator!.when(
|
||||
showDownload: (
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
@@ -241,6 +235,13 @@ class _LocalLLMInfoWidget extends StatelessWidget {
|
||||
},
|
||||
finishDownload: () => const InitLocalAIIndicator(),
|
||||
checkPluginState: () => const PluginStateIndicator(),
|
||||
startOfflineAIApp: () => OpenOrDownloadOfflineAIApp(
|
||||
onRetry: () {
|
||||
context
|
||||
.read<LocalAIChatSettingBloc>()
|
||||
.add(const LocalAIChatSettingEvent.refreshAISetting());
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
@@ -266,44 +267,11 @@ class _LocalLLMInfoWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showDownloadDialog(
|
||||
BuildContext context,
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) {
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
useRootNavigator: false,
|
||||
builder: (dialogContext) {
|
||||
return _LLMModelDownloadDialog(
|
||||
llmResource: llmResource,
|
||||
onOkPressed: () {
|
||||
context.read<LocalAIChatSettingBloc>().add(
|
||||
LocalAIChatSettingEvent.startDownloadModel(
|
||||
llmModel,
|
||||
),
|
||||
);
|
||||
},
|
||||
onCancelPressed: () {
|
||||
context.read<LocalAIChatSettingBloc>().add(
|
||||
const LocalAIChatSettingEvent.cancelDownload(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
debugLabel: 'localModel.download',
|
||||
);
|
||||
}
|
||||
|
||||
FlowyError? errorFromState(LocalAIChatSettingState state) {
|
||||
final err = state.fetchModelInfoState.when(
|
||||
final err = state.aiModelProgress.when(
|
||||
loading: () => null,
|
||||
finish: (err) => err,
|
||||
init: () {},
|
||||
);
|
||||
|
||||
if (err == null) {
|
||||
@@ -317,39 +285,48 @@ class _LocalLLMInfoWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _LLMModelDownloadDialog extends StatelessWidget {
|
||||
const _LLMModelDownloadDialog({
|
||||
required this.llmResource,
|
||||
required this.onOkPressed,
|
||||
required this.onCancelPressed,
|
||||
});
|
||||
final LocalModelResourcePB llmResource;
|
||||
final VoidCallback onOkPressed;
|
||||
final VoidCallback onCancelPressed;
|
||||
void _showDownloadDialog(
|
||||
BuildContext context,
|
||||
LocalModelResourcePB llmResource,
|
||||
LLMModelPB llmModel,
|
||||
) {
|
||||
if (llmResource.pendingResources.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigatorOkCancelDialog(
|
||||
title: LocaleKeys.settings_aiPage_keys_downloadLLMPrompt.tr(
|
||||
final res = llmResource.pendingResources.first;
|
||||
String desc = "";
|
||||
switch (res.resType) {
|
||||
case PendingResourceTypePB.AIModel:
|
||||
desc = LocaleKeys.settings_aiPage_keys_downloadLLMPromptDetail.tr(
|
||||
args: [
|
||||
llmResource.pendingResources[0].name,
|
||||
llmResource.pendingResources[0].fileSize,
|
||||
],
|
||||
),
|
||||
message: llmResource.pendingResources[0].fileSize == 0
|
||||
? ""
|
||||
: LocaleKeys.settings_aiPage_keys_downloadLLMPromptDetail.tr(
|
||||
args: [
|
||||
llmResource.pendingResources[0].name,
|
||||
llmResource.pendingResources[0].fileSize.toString(),
|
||||
],
|
||||
),
|
||||
okTitle: LocaleKeys.button_confirm.tr(),
|
||||
cancelTitle: LocaleKeys.button_cancel.tr(),
|
||||
onOkPressed: onOkPressed,
|
||||
onCancelPressed: onCancelPressed,
|
||||
titleUpperCase: false,
|
||||
);
|
||||
);
|
||||
break;
|
||||
case PendingResourceTypePB.OfflineApp:
|
||||
desc = LocaleKeys.settings_aiPage_keys_downloadAppFlowyOfflineAI.tr();
|
||||
break;
|
||||
}
|
||||
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
style: ConfirmPopupStyle.cancelAndOk,
|
||||
title: LocaleKeys.settings_aiPage_keys_downloadLLMPrompt.tr(
|
||||
args: [res.name],
|
||||
),
|
||||
description: desc,
|
||||
confirmLabel: LocaleKeys.button_confirm.tr(),
|
||||
onConfirm: () => context.read<LocalAIChatSettingBloc>().add(
|
||||
LocalAIChatSettingEvent.startDownloadModel(
|
||||
llmModel,
|
||||
),
|
||||
),
|
||||
onCancel: () => context.read<LocalAIChatSettingBloc>().add(
|
||||
const LocalAIChatSettingEvent.cancelDownload(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _ShowDownloadIndicator extends StatelessWidget {
|
||||
@@ -381,29 +358,7 @@ class _ShowDownloadIndicator extends StatelessWidget {
|
||||
color: Color(0xFF005483),
|
||||
),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
useRootNavigator: false,
|
||||
builder: (dialogContext) {
|
||||
return _LLMModelDownloadDialog(
|
||||
llmResource: llmResource,
|
||||
onOkPressed: () {
|
||||
context.read<LocalAIChatSettingBloc>().add(
|
||||
LocalAIChatSettingEvent.startDownloadModel(
|
||||
llmModel,
|
||||
),
|
||||
);
|
||||
},
|
||||
onCancelPressed: () {
|
||||
context.read<LocalAIChatSettingBloc>().add(
|
||||
const LocalAIChatSettingEvent
|
||||
.cancelDownload(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
_showDownloadDialog(context, llmResource, llmModel);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
+14
-37
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/local_ai_chat_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
@@ -7,9 +10,6 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class LocalAISetting extends StatefulWidget {
|
||||
@@ -108,20 +108,18 @@ class LocalAISettingHeader extends StatelessWidget {
|
||||
value: isEnabled,
|
||||
onChanged: (value) {
|
||||
if (isEnabled) {
|
||||
showDialog(
|
||||
showConfirmDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
useRootNavigator: false,
|
||||
builder: (dialogContext) {
|
||||
return _ToggleLocalAIDialog(
|
||||
onOkPressed: () {
|
||||
context
|
||||
.read<LocalAIToggleBloc>()
|
||||
.add(const LocalAIToggleEvent.toggle());
|
||||
},
|
||||
onCancelPressed: () {},
|
||||
);
|
||||
},
|
||||
title: LocaleKeys
|
||||
.settings_aiPage_keys_disableLocalAITitle
|
||||
.tr(),
|
||||
description: LocaleKeys
|
||||
.settings_aiPage_keys_disableLocalAIDescription
|
||||
.tr(),
|
||||
confirmLabel: LocaleKeys.button_confirm.tr(),
|
||||
onConfirm: () => context
|
||||
.read<LocalAIToggleBloc>()
|
||||
.add(const LocalAIToggleEvent.toggle()),
|
||||
);
|
||||
} else {
|
||||
context
|
||||
@@ -138,24 +136,3 @@ class LocalAISettingHeader extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ToggleLocalAIDialog extends StatelessWidget {
|
||||
const _ToggleLocalAIDialog({
|
||||
required this.onOkPressed,
|
||||
required this.onCancelPressed,
|
||||
});
|
||||
final VoidCallback onOkPressed;
|
||||
final VoidCallback onCancelPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigatorOkCancelDialog(
|
||||
message: LocaleKeys.settings_aiPage_keys_disableLocalAIDialog.tr(),
|
||||
okTitle: LocaleKeys.button_confirm.tr(),
|
||||
cancelTitle: LocaleKeys.button_cancel.tr(),
|
||||
onOkPressed: onOkPressed,
|
||||
onCancelPressed: onCancelPressed,
|
||||
titleUpperCase: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
+124
-4
@@ -1,10 +1,14 @@
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/download_offline_ai_app_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/plugin_state_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -21,8 +25,15 @@ class PluginStateIndicator extends StatelessWidget {
|
||||
return state.action.when(
|
||||
init: () => const _InitPlugin(),
|
||||
ready: () => const _LocalAIReadyToUse(),
|
||||
restart: () => const _ReloadButton(),
|
||||
restartPlugin: () => const _ReloadButton(),
|
||||
loadingPlugin: () => const _InitPlugin(),
|
||||
startAIOfflineApp: () => OpenOrDownloadOfflineAIApp(
|
||||
onRetry: () {
|
||||
context
|
||||
.read<PluginStateBloc>()
|
||||
.add(const PluginStateEvent.started());
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -35,9 +46,15 @@ class _InitPlugin extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox(
|
||||
height: 20,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
return Row(
|
||||
children: [
|
||||
FlowyText(LocaleKeys.settings_aiPage_keys_localAIStart.tr()),
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -124,3 +141,106 @@ class _LocalAIReadyToUse extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenOrDownloadOfflineAIApp extends StatelessWidget {
|
||||
const OpenOrDownloadOfflineAIApp({required this.onRetry, super.key});
|
||||
|
||||
final VoidCallback onRetry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => DownloadOfflineAIBloc(),
|
||||
child: BlocBuilder<DownloadOfflineAIBloc, DownloadOfflineAIState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RichText(
|
||||
maxLines: 3,
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text:
|
||||
"${LocaleKeys.settings_aiPage_keys_offlineAIInstruction1.tr()} ",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(height: 1.5),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
" ${LocaleKeys.settings_aiPage_keys_offlineAIInstruction2.tr()} ",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: FontSizes.s14,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 1.5,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => afLaunchUrlString(
|
||||
"https://docs.appflowy.io/docs/appflowy/product/appflowy-ai-offline",
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
" ${LocaleKeys.settings_aiPage_keys_offlineAIInstruction3.tr()} ",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(height: 1.5),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
"${LocaleKeys.settings_aiPage_keys_offlineAIDownload1.tr()} ",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(height: 1.5),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
" ${LocaleKeys.settings_aiPage_keys_offlineAIDownload2.tr()} ",
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: FontSizes.s14,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 1.5,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap =
|
||||
() => context.read<DownloadOfflineAIBloc>().add(
|
||||
const DownloadOfflineAIEvent.started(),
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
" ${LocaleKeys.settings_aiPage_keys_offlineAIDownload3.tr()} ",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall!
|
||||
.copyWith(height: 1.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 6,
|
||||
), // Replaced VSpace with SizedBox for simplicity
|
||||
SizedBox(
|
||||
height: 30,
|
||||
child: FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12),
|
||||
text: FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_activeOfflineAI.tr(),
|
||||
),
|
||||
onTap: onRetry,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+8
-7
@@ -7,7 +7,6 @@ import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/m
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -53,11 +52,12 @@ class SettingsAIView extends StatelessWidget {
|
||||
];
|
||||
|
||||
children.add(const _AISearchToggle(value: false));
|
||||
children.add(
|
||||
_LocalAIOnBoarding(
|
||||
workspaceId: userProfile.workspaceId,
|
||||
),
|
||||
);
|
||||
// TODO(nathan): enable local ai
|
||||
// children.add(
|
||||
// _LocalAIOnBoarding(
|
||||
// workspaceId: userProfile.workspaceId,
|
||||
// ),
|
||||
// );
|
||||
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_aiPage_title.tr(),
|
||||
@@ -114,6 +114,7 @@ class _AISearchToggle extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
class _LocalAIOnBoarding extends StatelessWidget {
|
||||
const _LocalAIOnBoarding({required this.workspaceId});
|
||||
final String workspaceId;
|
||||
@@ -129,7 +130,7 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
child: BlocBuilder<LocalAIOnBoardingBloc, LocalAIOnBoardingState>(
|
||||
builder: (context, state) {
|
||||
// Show the local AI settings if the user has purchased the AI Local plan
|
||||
if (kDebugMode || state.isPurchaseAILocal) {
|
||||
if (state.isPurchaseAILocal) {
|
||||
return const LocalAISetting();
|
||||
} else {
|
||||
// Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan
|
||||
|
||||
+70
-69
@@ -1,6 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -22,6 +19,8 @@ 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:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SettingsAccountView extends StatefulWidget {
|
||||
@@ -244,80 +243,82 @@ class SignInOutButton extends StatelessWidget {
|
||||
child: Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Row(
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.arrow_back_m,
|
||||
size: Size.square(24),
|
||||
),
|
||||
const HSpace(8),
|
||||
FlowyText.semibold(
|
||||
LocaleKeys.button_back.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.m_close_m,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_accountPage_login_loginLabel
|
||||
.tr(),
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
maxLines: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(16),
|
||||
const SignInWithMagicLinkButtons(),
|
||||
if (isAuthEnabled) ...[
|
||||
const VSpace(20),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Row(
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.arrow_back_m,
|
||||
size: Size.square(24),
|
||||
),
|
||||
const HSpace(8),
|
||||
FlowyText.semibold(
|
||||
LocaleKeys.button_back.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.signIn_or.tr(),
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: Navigator.of(context).pop,
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: FlowySvg(
|
||||
FlowySvgs.m_close_m,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
],
|
||||
),
|
||||
const VSpace(10),
|
||||
SettingThirdPartyLogin(didLogin: onAction),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_accountPage_login_loginLabel
|
||||
.tr(),
|
||||
fontSize: 22,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
maxLines: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(16),
|
||||
const SignInWithMagicLinkButtons(),
|
||||
if (isAuthEnabled) ...[
|
||||
const VSpace(20),
|
||||
Row(
|
||||
children: [
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
),
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.signIn_or.tr(),
|
||||
),
|
||||
),
|
||||
const Flexible(child: Divider(thickness: 1)),
|
||||
],
|
||||
),
|
||||
const VSpace(10),
|
||||
SettingThirdPartyLogin(didLogin: onAction),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+18
-19
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/util/int64_extension.dart';
|
||||
@@ -214,23 +212,24 @@ class _SettingsBillingViewState extends State<SettingsBillingView> {
|
||||
|
||||
// Currently, the AI Local tile is only available on macOS
|
||||
// TODO(nathan): enable windows and linux
|
||||
if (Platform.isMacOS)
|
||||
_AITile(
|
||||
plan: SubscriptionPlanPB.AiLocal,
|
||||
label: LocaleKeys
|
||||
.settings_billingPage_addons_aiOnDevice_label
|
||||
.tr(),
|
||||
description: LocaleKeys
|
||||
.settings_billingPage_addons_aiOnDevice_description,
|
||||
activeDescription: LocaleKeys
|
||||
.settings_billingPage_addons_aiOnDevice_activeDescription,
|
||||
canceledDescription: LocaleKeys
|
||||
.settings_billingPage_addons_aiOnDevice_canceledDescription,
|
||||
subscriptionInfo:
|
||||
state.subscriptionInfo.addOns.firstWhereOrNull(
|
||||
(a) => a.type == WorkspaceAddOnPBType.AddOnAiLocal,
|
||||
),
|
||||
),
|
||||
// TODO(nathan): enable local ai
|
||||
// if (Platform.isMacOS)
|
||||
// _AITile(
|
||||
// plan: SubscriptionPlanPB.AiLocal,
|
||||
// label: LocaleKeys
|
||||
// .settings_billingPage_addons_aiOnDevice_label
|
||||
// .tr(),
|
||||
// description: LocaleKeys
|
||||
// .settings_billingPage_addons_aiOnDevice_description,
|
||||
// activeDescription: LocaleKeys
|
||||
// .settings_billingPage_addons_aiOnDevice_activeDescription,
|
||||
// canceledDescription: LocaleKeys
|
||||
// .settings_billingPage_addons_aiOnDevice_canceledDescription,
|
||||
// subscriptionInfo:
|
||||
// state.subscriptionInfo.addOns.firstWhereOrNull(
|
||||
// (a) => a.type == WorkspaceAddOnPBType.AddOnAiLocal,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
+38
-39
@@ -1,5 +1,3 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
@@ -141,43 +139,44 @@ class _SettingsPlanViewState extends State<SettingsPlanView> {
|
||||
|
||||
// Currently, the AI Local tile is only available on macOS
|
||||
// TODO(nathan): enable windows and linux
|
||||
if (Platform.isMacOS)
|
||||
Flexible(
|
||||
child: _AddOnBox(
|
||||
title: LocaleKeys
|
||||
.settings_planPage_planUsage_addons_aiOnDevice_title
|
||||
.tr(),
|
||||
description: LocaleKeys
|
||||
.settings_planPage_planUsage_addons_aiOnDevice_description
|
||||
.tr(),
|
||||
price: LocaleKeys
|
||||
.settings_planPage_planUsage_addons_aiOnDevice_price
|
||||
.tr(
|
||||
args: [
|
||||
SubscriptionPlanPB.AiLocal.priceAnnualBilling,
|
||||
],
|
||||
),
|
||||
priceInfo: LocaleKeys
|
||||
.settings_planPage_planUsage_addons_aiOnDevice_priceInfo
|
||||
.tr(),
|
||||
billingInfo: LocaleKeys
|
||||
.settings_planPage_planUsage_addons_aiOnDevice_billingInfo
|
||||
.tr(
|
||||
args: [
|
||||
SubscriptionPlanPB.AiLocal.priceMonthBilling,
|
||||
],
|
||||
),
|
||||
buttonText: state.subscriptionInfo.hasAIOnDevice
|
||||
? LocaleKeys
|
||||
.settings_planPage_planUsage_addons_activeLabel
|
||||
.tr()
|
||||
: LocaleKeys
|
||||
.settings_planPage_planUsage_addons_addLabel
|
||||
.tr(),
|
||||
isActive: state.subscriptionInfo.hasAIOnDevice,
|
||||
plan: SubscriptionPlanPB.AiLocal,
|
||||
),
|
||||
),
|
||||
// TODO(nathan): enable local ai
|
||||
// if (Platform.isMacOS)
|
||||
// Flexible(
|
||||
// child: _AddOnBox(
|
||||
// title: LocaleKeys
|
||||
// .settings_planPage_planUsage_addons_aiOnDevice_title
|
||||
// .tr(),
|
||||
// description: LocaleKeys
|
||||
// .settings_planPage_planUsage_addons_aiOnDevice_description
|
||||
// .tr(),
|
||||
// price: LocaleKeys
|
||||
// .settings_planPage_planUsage_addons_aiOnDevice_price
|
||||
// .tr(
|
||||
// args: [
|
||||
// SubscriptionPlanPB.AiLocal.priceAnnualBilling,
|
||||
// ],
|
||||
// ),
|
||||
// priceInfo: LocaleKeys
|
||||
// .settings_planPage_planUsage_addons_aiOnDevice_priceInfo
|
||||
// .tr(),
|
||||
// billingInfo: LocaleKeys
|
||||
// .settings_planPage_planUsage_addons_aiOnDevice_billingInfo
|
||||
// .tr(
|
||||
// args: [
|
||||
// SubscriptionPlanPB.AiLocal.priceMonthBilling,
|
||||
// ],
|
||||
// ),
|
||||
// buttonText: state.subscriptionInfo.hasAIOnDevice
|
||||
// ? LocaleKeys
|
||||
// .settings_planPage_planUsage_addons_activeLabel
|
||||
// .tr()
|
||||
// : LocaleKeys
|
||||
// .settings_planPage_planUsage_addons_addLabel
|
||||
// .tr(),
|
||||
// isActive: state.subscriptionInfo.hasAIOnDevice,
|
||||
// plan: SubscriptionPlanPB.AiLocal,
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_billing_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_manage_data_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_view.dart';
|
||||
@@ -25,7 +25,7 @@ class SettingsDialog extends StatelessWidget {
|
||||
required this.dismissDialog,
|
||||
required this.didLogout,
|
||||
required this.restartApp,
|
||||
this.initPage,
|
||||
this.initPage,
|
||||
}) : super(key: ValueKey(user.id));
|
||||
|
||||
final VoidCallback dismissDialog;
|
||||
@@ -39,6 +39,7 @@ class SettingsDialog extends StatelessWidget {
|
||||
return BlocProvider<SettingsDialogBloc>(
|
||||
create: (context) => SettingsDialogBloc(
|
||||
user,
|
||||
context.read<UserWorkspaceBloc>().state.currentWorkspaceMember,
|
||||
initPage: initPage,
|
||||
)..add(const SettingsDialogEvent.initial()),
|
||||
child: BlocBuilder<SettingsDialogBloc, SettingsDialogState>(
|
||||
@@ -60,6 +61,7 @@ class SettingsDialog extends StatelessWidget {
|
||||
.add(SettingsDialogEvent.setSelectedPage(index)),
|
||||
currentPage:
|
||||
context.read<SettingsDialogBloc>().state.page,
|
||||
isBillingEnabled: state.isBillingEnabled,
|
||||
member: context
|
||||
.read<UserWorkspaceBloc>()
|
||||
.state
|
||||
|
||||
+21
-20
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -13,7 +12,9 @@ import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_bu
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -355,25 +356,25 @@ class BillingGateGuard extends StatelessWidget {
|
||||
|
||||
Future<bool> isBillingEnabled() async {
|
||||
final result = await UserEventGetCloudConfig().send();
|
||||
return result.fold((cloudSetting) {
|
||||
final whiteList = [
|
||||
"https://beta.appflowy.cloud",
|
||||
"https://test.appflowy.cloud",
|
||||
];
|
||||
if (kDebugMode) {
|
||||
whiteList.add("http://localhost:8000");
|
||||
}
|
||||
return result.fold(
|
||||
(cloudSetting) {
|
||||
final whiteList = [
|
||||
"https://beta.appflowy.cloud",
|
||||
"https://test.appflowy.cloud",
|
||||
];
|
||||
if (kDebugMode) {
|
||||
whiteList.add("http://localhost:8000");
|
||||
}
|
||||
|
||||
if (whiteList.contains(cloudSetting.serverUrl)) {
|
||||
return true;
|
||||
} else {
|
||||
Log.warn(
|
||||
"Billing is not enabled for this server:${cloudSetting.serverUrl}",
|
||||
);
|
||||
final isWhiteListed = whiteList.contains(cloudSetting.serverUrl);
|
||||
if (!isWhiteListed) {
|
||||
Log.warn("Billing is not enabled for server ${cloudSetting.serverUrl}");
|
||||
}
|
||||
return isWhiteListed;
|
||||
},
|
||||
(err) {
|
||||
Log.error("Failed to get cloud config: $err");
|
||||
return false;
|
||||
}
|
||||
}, (err) {
|
||||
Log.error("Failed to get cloud config: $err");
|
||||
return false;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
+3
-6
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||
@@ -17,12 +16,14 @@ class SettingsMenu extends StatelessWidget {
|
||||
required this.changeSelectedPage,
|
||||
required this.currentPage,
|
||||
required this.userProfile,
|
||||
required this.isBillingEnabled,
|
||||
this.member,
|
||||
});
|
||||
|
||||
final Function changeSelectedPage;
|
||||
final SettingsPage currentPage;
|
||||
final UserProfilePB userProfile;
|
||||
final bool isBillingEnabled;
|
||||
final WorkspaceMemberPB? member;
|
||||
|
||||
@override
|
||||
@@ -112,11 +113,7 @@ class SettingsMenu extends StatelessWidget {
|
||||
),
|
||||
changeSelectedPage: changeSelectedPage,
|
||||
),
|
||||
if (FeatureFlag.planBilling.isOn &&
|
||||
userProfile.authenticator ==
|
||||
AuthenticatorPB.AppFlowyCloud &&
|
||||
member != null &&
|
||||
member!.role.isOwner) ...[
|
||||
if (FeatureFlag.planBilling.isOn && isBillingEnabled) ...[
|
||||
SettingsMenuElement(
|
||||
page: SettingsPage.plan,
|
||||
selectedPage: currentPage,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
@@ -11,6 +11,7 @@ import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
@@ -303,6 +304,18 @@ void showToastNotification(
|
||||
String? description,
|
||||
ToastificationType type = ToastificationType.success,
|
||||
}) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
toastification.showCustom(
|
||||
alignment: Alignment.bottomCenter,
|
||||
autoCloseDuration: const Duration(milliseconds: 3000),
|
||||
builder: (_, __) => _MToast(
|
||||
message: message,
|
||||
type: type,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
toastification.show(
|
||||
context: context,
|
||||
type: type,
|
||||
@@ -329,6 +342,50 @@ void showToastNotification(
|
||||
);
|
||||
}
|
||||
|
||||
class _MToast extends StatelessWidget {
|
||||
const _MToast({
|
||||
required this.message,
|
||||
this.type = ToastificationType.success,
|
||||
});
|
||||
|
||||
final String message;
|
||||
final ToastificationType type;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// only support success type
|
||||
assert(type == ToastificationType.success);
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
padding: const EdgeInsets.only(bottom: 100),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 13.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: const Color(0xE5171717),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.success_s,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
FlowyText.regular(
|
||||
message,
|
||||
fontSize: 16.0,
|
||||
figmaLineHeight: 18.0,
|
||||
color: Colors.white,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showConfirmDeletionDialog({
|
||||
required BuildContext context,
|
||||
required String name,
|
||||
@@ -361,6 +418,7 @@ Future<void> showConfirmDialog({
|
||||
required String title,
|
||||
required String description,
|
||||
VoidCallback? onConfirm,
|
||||
VoidCallback? onCancel,
|
||||
String? confirmLabel,
|
||||
ConfirmPopupStyle style = ConfirmPopupStyle.onlyOk,
|
||||
}) {
|
||||
@@ -377,6 +435,7 @@ Future<void> showConfirmDialog({
|
||||
title: title,
|
||||
description: description,
|
||||
onConfirm: () => onConfirm?.call(),
|
||||
onCancel: () => onCancel?.call(),
|
||||
confirmLabel: confirmLabel,
|
||||
style: style,
|
||||
),
|
||||
|
||||
+7
-5
@@ -1,6 +1,8 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -14,7 +16,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart';
|
||||
|
||||
@@ -190,7 +191,7 @@ class InteractiveImageToolbar extends StatelessWidget {
|
||||
tooltip: LocaleKeys
|
||||
.document_imageBlock_interactiveViewer_toolbar_closeViewer
|
||||
.tr(),
|
||||
icon: FlowySvgs.close_s,
|
||||
icon: FlowySvgs.close_viewer_s,
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
@@ -291,11 +292,12 @@ class _ToolbarItem extends StatelessWidget {
|
||||
isDisabled ? Colors.transparent : Colors.white.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: FlowySvg(
|
||||
icon,
|
||||
size: const Size.square(16),
|
||||
color: isDisabled ? Colors.grey : Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
+8
-4
@@ -110,10 +110,14 @@ class _InteractiveImageViewerState extends State<InteractiveImageViewer> {
|
||||
child: SizedBox(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: widget.imageProvider.renderImage(
|
||||
context,
|
||||
currentIndex,
|
||||
userProfile,
|
||||
child: GestureDetector(
|
||||
// We can consider adding zoom behavior instead in a later iteration
|
||||
onDoubleTap: () => Navigator.of(context).pop(),
|
||||
child: widget.imageProvider.renderImage(
|
||||
context,
|
||||
currentIndex,
|
||||
userProfile,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
||||
@@ -311,7 +310,7 @@ class FlowyTextButton extends StatelessWidget {
|
||||
constraints: const BoxConstraints(minHeight: 32),
|
||||
fillColor: Theme.of(context).colorScheme.primary,
|
||||
hoverColor: const Color(0xFF005483),
|
||||
fontColor: AFThemeExtension.of(context).strongText,
|
||||
fontColor: Theme.of(context).colorScheme.onPrimary,
|
||||
fontHoverColor: Colors.white,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
@@ -201,7 +202,7 @@ class FlowyText extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (withTooltip) {
|
||||
child = Tooltip(
|
||||
child = FlowyTooltip(
|
||||
message: text,
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -53,8 +53,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "268aae9"
|
||||
resolved-ref: "268aae905b18efc8a3a9c88dc75ebd19b314bd43"
|
||||
ref: aac7729
|
||||
resolved-ref: aac77292a1a175fd7450eef30167032d3cec7fea
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "3.1.0"
|
||||
@@ -806,6 +806,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
flutter_staggered_grid_view:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_staggered_grid_view
|
||||
sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
flutter_sticky_header:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1670,10 +1678,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
|
||||
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
realtime_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2443,5 +2451,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
sdks:
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
@@ -149,6 +149,8 @@ dependencies:
|
||||
desktop_drop: ^0.4.4
|
||||
cross_file: ^0.3.4+1
|
||||
|
||||
flutter_staggered_grid_view: ^0.7.0
|
||||
|
||||
# Window Manager for MacOS and Linux
|
||||
window_manager: ^0.3.9
|
||||
|
||||
@@ -194,7 +196,7 @@ dependency_overrides:
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: "268aae9"
|
||||
ref: "aac7729"
|
||||
|
||||
appflowy_editor_plugins:
|
||||
git:
|
||||
|
||||
+26
-22
@@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -192,7 +192,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -206,7 +206,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-local-ai"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=f3b678e36f22012b241f8e2f3cb811be2da245c0#f3b678e36f22012b241f8e2f3cb811be2da245c0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec#8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"appflowy-plugin",
|
||||
@@ -225,7 +225,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-plugin"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=f3b678e36f22012b241f8e2f3cb811be2da245c0#f3b678e36f22012b241f8e2f3cb811be2da245c0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec#8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -826,7 +826,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -876,7 +876,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@@ -888,7 +888,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -962,7 +962,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -986,7 +986,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1016,7 +1016,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -1036,22 +1036,26 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"collab",
|
||||
"getrandom 0.2.10",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"protoc-bin-vendored",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1089,7 +1093,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -1128,7 +1132,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1153,7 +1157,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1170,7 +1174,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -1417,7 +1421,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa 1.0.6",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -1528,7 +1532,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3034,7 +3038,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -3051,7 +3055,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3483,7 +3487,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -6027,7 +6031,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@@ -53,7 +53,7 @@ collab-user = { version = "0.2" }
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "0062c950677f7f633f5b7edabc827a35d3bc92c3" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
@@ -116,17 +116,17 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
|
||||
# Working directory: frontend
|
||||
# To update the commit ID, run:
|
||||
# scripts/tool/update_local_ai_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "f3b678e36f22012b241f8e2f3cb811be2da245c0" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "f3b678e36f22012b241f8e2f3cb811be2da245c0" }
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec" }
|
||||
+26
-22
@@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -183,7 +183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -197,7 +197,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-local-ai"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=f3b678e36f22012b241f8e2f3cb811be2da245c0#f3b678e36f22012b241f8e2f3cb811be2da245c0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec#8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"appflowy-plugin",
|
||||
@@ -216,7 +216,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-plugin"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=f3b678e36f22012b241f8e2f3cb811be2da245c0#f3b678e36f22012b241f8e2f3cb811be2da245c0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec#8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -800,7 +800,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -850,7 +850,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@@ -862,7 +862,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -945,7 +945,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -969,7 +969,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -999,7 +999,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -1019,22 +1019,26 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"collab",
|
||||
"getrandom 0.2.12",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"protoc-bin-vendored",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1072,7 +1076,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -1111,7 +1115,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1136,7 +1140,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1153,7 +1157,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -1407,7 +1411,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa 1.0.10",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -1518,7 +1522,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3101,7 +3105,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -3118,7 +3122,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3555,7 +3559,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -6091,7 +6095,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "0062c950677f7f633f5b7edabc827a35d3bc92c3" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
@@ -116,18 +116,18 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
|
||||
# Working directory: frontend
|
||||
# To update the commit ID, run:
|
||||
# scripts/tool/update_local_ai_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "f3b678e36f22012b241f8e2f3cb811be2da245c0" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "f3b678e36f22012b241f8e2f3cb811be2da245c0" }
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec" }
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="11.6765" y="3.27295" width="1.48562" height="11.8849" rx="0.742808" transform="rotate(45 11.6765 3.27295)" fill="#333333"/>
|
||||
<rect x="12.7271" y="11.6766" width="1.48562" height="11.8849" rx="0.742808" transform="rotate(135 12.7271 11.6766)" fill="#333333"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 370 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#e8eaed"><path d="M215.74-528Q186-528 165-549.5 144-571 144-600v-144q0-29.7 21.18-50.85Q186.35-816 216.09-816h144.17Q390-816 411-794.85q21 21.15 21 50.85v144q0 29-21.18 50.5-21.17 21.5-50.91 21.5H215.74Zm0 384Q186-144 165-165.18q-21-21.17-21-50.91v-144.17Q144-390 165.18-411q21.17-21 50.91-21h144.17Q390-432 411-410.82q21 21.17 21 50.91v144.17Q432-186 410.82-165q-21.17 21-50.91 21H215.74ZM600-528q-29 0-50.5-21.5T528-600v-144q0-29.7 21.5-50.85Q571-816 600-816h144q29.7 0 50.85 21.15Q816-773.7 816-744v144q0 29-21.15 50.5T744-528H600Zm0 384q-29 0-50.5-21.18-21.5-21.17-21.5-50.91v-144.17Q528-390 549.5-411q21.5-21 50.5-21h144q29.7 0 50.85 21.18Q816-389.65 816-359.91v144.17Q816-186 794.85-165 773.7-144 744-144H600ZM216-600h144v-144H216v144Zm384 0h144v-144H600v144Zm0 384h144v-144H600v144Zm-384 0h144v-144H216v144Zm384-384Zm0 240Zm-240 0Zm0-240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 952 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.64912 1.71521C2.64912 1.43942 2.84766 1.2002 3.16 1.2002H8.855C9.125 1.2002 9.36707 1.40199 9.36707 1.71021C9.36707 1.89847 9.20994 2.20021 8.855 2.20021L8.01019 2.20277L8.00984 4.46598C8.00984 4.53315 8.05692 4.59785 8.10288 4.64382L10.1752 7.02856C10.223 7.07629 10.25 7.14117 10.25 7.20834L10.2495 7.61027C10.25 7.89027 10.025 8.11628 9.75 8.11628L6.51011 8.11753L6.51 11.2002C6.50999 11.4763 6.28614 11.7002 6.01 11.7002H5.99949C5.72355 11.7002 5.49977 11.4761 5.49949 11.2002L5.49613 8.11681L2.255 8.11631C1.96831 8.11631 1.75 7.89027 1.75 7.61027L1.75046 7.20898C1.75048 7.14179 1.77659 7.07678 1.82409 7.02928L3.90196 4.64452C3.94951 4.59697 4.00575 4.53192 4.00575 4.46474L4.00593 2.20171L3.16 2.20021C2.89258 2.20171 2.64912 1.9824 2.64912 1.71521Z" fill="#4DA594"/>
|
||||
<path d="M2.64912 1.71521C2.64912 1.43942 2.84766 1.2002 3.16 1.2002H8.855C9.125 1.2002 9.36707 1.40199 9.36707 1.71021C9.36707 1.89847 9.20994 2.20021 8.855 2.20021L8.01019 2.20277L8.00984 4.46598C8.00984 4.53315 8.05692 4.59785 8.10288 4.64382L10.1752 7.02856C10.223 7.07629 10.25 7.14117 10.25 7.20834L10.2495 7.61027C10.25 7.89027 10.025 8.11628 9.75 8.11628L6.51011 8.11753L6.51 11.2002C6.50999 11.4763 6.28614 11.7002 6.01 11.7002H5.99949C5.72355 11.7002 5.49977 11.4761 5.49949 11.2002L5.49613 8.11681L2.255 8.11631C1.96831 8.11631 1.75 7.89027 1.75 7.61027L1.75046 7.20898C1.75048 7.14179 1.77659 7.07678 1.82409 7.02928L3.90196 4.64452C3.94951 4.59697 4.00575 4.53192 4.00575 4.46474L4.00593 2.20171L3.16 2.20021C2.89258 2.20171 2.64912 1.9824 2.64912 1.71521Z" fill="#F65B6E"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 891 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#e8eaed"><path d="M96-384v-192q0-29.7 21.21-50.85 21.21-21.15 51-21.15T219-626.85q21 21.15 21 50.85v192q0 29.7-21.21 50.85-21.21 21.15-51 21.15T117-333.15Q96-354.3 96-384Zm264 144q-33 0-52.5-19.5T288-312v-336q0-33 19.5-52.5T360-720h240q33 0 52.5 19.5T672-648v336q0 33-19.5 52.5T600-240H360Zm360-144v-192q0-29.7 21.21-50.85 21.21-21.15 51-21.15T843-626.85q21 21.15 21 50.85v192q0 29.7-21.21 50.85-21.21 21.15-51 21.15T741-333.15Q720-354.3 720-384Zm-360 72h240v-336H360v336Zm120-168Z"/></svg>
|
||||
|
After Width: | Height: | Size: 588 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#e8eaed"><path d="M216-144q-29 0-50.5-21.5T144-216v-528q0-29.7 21.5-50.85Q187-816 216-816h528q29.7 0 50.85 21.15Q816-773.7 816-744v528q0 29-21.15 50.5T744-144H216Zm0-72h228v-528H216v528Zm300 0h228v-264H516v264Zm0-336h228v-192H516v192Z"/></svg>
|
||||
|
After Width: | Height: | Size: 341 B |
@@ -159,6 +159,7 @@
|
||||
"chat": {
|
||||
"newChat": "AI Chat",
|
||||
"inputMessageHint": "Message @:appName AI",
|
||||
"inputLocalAIMessageHint": "Message @:appName Local AI",
|
||||
"unsupportedCloudPrompt": "This feature is only available when using @:appName Cloud",
|
||||
"relatedQuestion": "Related",
|
||||
"serverUnavailable": "Service Temporarily Unavailable. Please try again later.",
|
||||
@@ -276,7 +277,7 @@
|
||||
"justNow": "just now",
|
||||
"minutesAgo": "{count} minutes ago",
|
||||
"lastViewed": "Last viewed",
|
||||
"favoriteAt": "Favorited at",
|
||||
"favoriteAt": "Favorited",
|
||||
"emptyRecent": "No Recent Documents",
|
||||
"emptyRecentDescription": "As you view documents, they will appear here for easy retrieval",
|
||||
"emptyFavorite": "No Favorite Documents",
|
||||
@@ -337,6 +338,8 @@
|
||||
"removeFromFavorites": "Remove from favorites",
|
||||
"removeFromRecent": "Remove from recent",
|
||||
"addToFavorites": "Add to favorites",
|
||||
"favoriteSuccessfully": "Favorited success",
|
||||
"unfavoriteSuccessfully": "Unfavorited success",
|
||||
"rename": "Rename",
|
||||
"helpCenter": "Help Center",
|
||||
"add": "Add",
|
||||
@@ -652,6 +655,7 @@
|
||||
"llmModel": "Language Model",
|
||||
"llmModelType": "Language Model Type",
|
||||
"downloadLLMPrompt": "Download {}",
|
||||
"downloadAppFlowyOfflineAI": "Downloading AI offline package will enable AI to run on your device. Do you want to continue?",
|
||||
"downloadLLMPromptDetail": "Downloading {} local model will take up to {} of storage. Do you want to continue?",
|
||||
"downloadAIModelButton": "Download AI model",
|
||||
"downloadingModel": "Downloading",
|
||||
@@ -661,9 +665,17 @@
|
||||
"localAIStopped": "Local AI stopped",
|
||||
"failToLoadLocalAI": "Failed to start local AI",
|
||||
"restartLocalAI": "Restart Local AI",
|
||||
"disableLocalAIDialog": "Do you want to disable local AI?",
|
||||
"disableLocalAITitle": "Disable local AI",
|
||||
"disableLocalAIDescription": "Do you want to disable local AI?",
|
||||
"localAIToggleTitle": "Toggle to enable or disable local AI",
|
||||
"fetchLocalModel": "Fetch local model configuration",
|
||||
"offlineAIInstruction1": "Follow the",
|
||||
"offlineAIInstruction2": "instruction",
|
||||
"offlineAIInstruction3": "to enable offline AI.",
|
||||
"offlineAIDownload1": "If you have not downloaded the AppFlowy AI, please",
|
||||
"offlineAIDownload2": "download",
|
||||
"offlineAIDownload3": "it first",
|
||||
"activeOfflineAI": "Active",
|
||||
"downloadOfflineAI": "Download",
|
||||
"openModelDirectory": "Open folder"
|
||||
}
|
||||
},
|
||||
@@ -1502,7 +1514,11 @@
|
||||
"photoKeyword": "photo",
|
||||
"photoBrowserKeyword": "photo browser",
|
||||
"galleryKeyword": "gallery",
|
||||
"addImageTooltip": "Add image"
|
||||
"addImageTooltip": "Add image",
|
||||
"changeLayoutTooltip": "Change layout",
|
||||
"browserLayout": "Browser",
|
||||
"gridLayout": "Grid",
|
||||
"deleteBlockTooltip": "Delete whole gallery"
|
||||
},
|
||||
"math": {
|
||||
"copiedToPasteBoard": "The math equation has been copied to the clipboard"
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -152,7 +152,7 @@
|
||||
"moveTo": "移动",
|
||||
"addToFavorites": "添加到收藏夹",
|
||||
"copyLink": "复制链接",
|
||||
"changeIcon": "更改图表",
|
||||
"changeIcon": "更改图标",
|
||||
"collapseAllPages": "收起全部子页面"
|
||||
},
|
||||
"blankPageTitle": "空白页",
|
||||
|
||||
Generated
+116
-21
@@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -183,7 +183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -197,7 +197,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-local-ai"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=f3b678e36f22012b241f8e2f3cb811be2da245c0#f3b678e36f22012b241f8e2f3cb811be2da245c0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec#8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"appflowy-plugin",
|
||||
@@ -216,7 +216,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-plugin"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=f3b678e36f22012b241f8e2f3cb811be2da245c0#f3b678e36f22012b241f8e2f3cb811be2da245c0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec#8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -718,7 +718,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -768,7 +768,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@@ -780,7 +780,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -823,7 +823,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -847,7 +847,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-database"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -877,7 +877,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-document"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -897,22 +897,26 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-entity"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"collab",
|
||||
"getrandom 0.2.10",
|
||||
"prost",
|
||||
"prost-build",
|
||||
"protoc-bin-vendored",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "collab-folder"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -950,7 +954,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-plugins"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -989,7 +993,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1014,7 +1018,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1031,7 +1035,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-user"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=93ec9a38722d5ed0628e6d03a161e2de172967f3#93ec9a38722d5ed0628e6d03a161e2de172967f3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collab",
|
||||
@@ -1352,7 +1356,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -1735,6 +1739,18 @@ dependencies = [
|
||||
"getrandom 0.2.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.4.1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "finl_unicode"
|
||||
version = "1.2.0"
|
||||
@@ -1790,6 +1806,7 @@ dependencies = [
|
||||
"lib-infra",
|
||||
"log",
|
||||
"md5",
|
||||
"notify",
|
||||
"parking_lot 0.12.1",
|
||||
"protobuf",
|
||||
"reqwest",
|
||||
@@ -2453,6 +2470,15 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
@@ -2701,7 +2727,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -2718,7 +2744,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3083,7 +3109,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -3094,6 +3120,26 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
@@ -3177,6 +3223,26 @@ dependencies = [
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@@ -3579,6 +3645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -3638,6 +3705,25 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
@@ -4674,6 +4760,15 @@ dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.5"
|
||||
@@ -5229,7 +5324,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=0062c950677f7f633f5b7edabc827a35d3bc92c3#0062c950677f7f633f5b7edabc827a35d3bc92c3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@@ -99,8 +99,8 @@ zip = "2.1.3"
|
||||
# Run the script.add_workspace_members:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "0062c950677f7f633f5b7edabc827a35d3bc92c3" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "0062c950677f7f633f5b7edabc827a35d3bc92c3" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
@@ -135,17 +135,17 @@ rocksdb = { git = "https://github.com/LucasXu0/rust-rocksdb", rev = "21cf4a23ec1
|
||||
# To switch to the local path, run:
|
||||
# scripts/tool/update_collab_source.sh
|
||||
# ⚠️⚠️⚠️️
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "93ec9a38722d5ed0628e6d03a161e2de172967f3" }
|
||||
|
||||
# Working directory: frontend
|
||||
# To update the commit ID, run:
|
||||
# scripts/tool/update_local_ai_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "f3b678e36f22012b241f8e2f3cb811be2da245c0" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "f3b678e36f22012b241f8e2f3cb811be2da245c0" }
|
||||
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec" }
|
||||
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "8ef7d3e4c38fbf92ff9b3630fe79017e95a496ec" }
|
||||
@@ -1,6 +1,6 @@
|
||||
use bytes::Bytes;
|
||||
pub use client_api::entity::ai_dto::{
|
||||
AppFlowyAIPlugin, CompletionType, LLMModel, LocalAIConfig, ModelInfo, RelatedQuestion,
|
||||
AppFlowyOfflineAI, CompletionType, LLMModel, LocalAIConfig, ModelInfo, RelatedQuestion,
|
||||
RepeatedRelatedQuestion, StringOrMessage,
|
||||
};
|
||||
pub use client_api::entity::{
|
||||
|
||||
@@ -44,6 +44,9 @@ md5 = "0.7.0"
|
||||
zip = { workspace = true, features = ["deflate"] }
|
||||
zip-extensions = "0.8.0"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
|
||||
notify = "6.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
dotenv = "0.15.0"
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -21,7 +21,7 @@ pub trait ChatUserService: Send + Sync + 'static {
|
||||
fn device_id(&self) -> Result<String, FlowyError>;
|
||||
fn workspace_id(&self) -> Result<String, FlowyError>;
|
||||
fn sqlite_connection(&self, uid: i64) -> Result<DBConnection, FlowyError>;
|
||||
fn user_data_dir(&self) -> Result<PathBuf, FlowyError>;
|
||||
fn data_root_dir(&self) -> Result<PathBuf, FlowyError>;
|
||||
}
|
||||
|
||||
pub struct ChatManager {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::local_ai::local_llm_chat::LLMModelInfo;
|
||||
use appflowy_plugin::core::plugin::RunningState;
|
||||
|
||||
use crate::local_ai::local_llm_resource::PendingResource;
|
||||
use flowy_chat_pub::cloud::{
|
||||
ChatMessage, LLMModel, RelatedQuestion, RepeatedChatMessage, RepeatedRelatedQuestion,
|
||||
};
|
||||
@@ -360,16 +361,38 @@ pub struct PendingResourcePB {
|
||||
pub name: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub file_size: i64,
|
||||
pub file_size: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub requirements: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub res_type: PendingResourceTypePB,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf_Enum, PartialEq, Eq, Copy)]
|
||||
pub enum PendingResourceTypePB {
|
||||
#[default]
|
||||
OfflineApp = 0,
|
||||
AIModel = 1,
|
||||
}
|
||||
|
||||
impl From<PendingResource> for PendingResourceTypePB {
|
||||
fn from(value: PendingResource) -> Self {
|
||||
match value {
|
||||
PendingResource::OfflineApp { .. } => PendingResourceTypePB::OfflineApp,
|
||||
PendingResource::ModelInfoRes { .. } => PendingResourceTypePB::AIModel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Clone, Debug)]
|
||||
pub struct LocalAIPluginStatePB {
|
||||
#[pb(index = 1)]
|
||||
pub state: RunningStatePB,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub offline_ai_ready: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf_Enum, PartialEq, Eq, Copy)]
|
||||
@@ -416,3 +439,9 @@ pub struct LocalModelStoragePB {
|
||||
#[pb(index = 1)]
|
||||
pub file_path: String,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Clone, Debug)]
|
||||
pub struct OfflineAIPB {
|
||||
#[pb(index = 1)]
|
||||
pub link: String,
|
||||
}
|
||||
@@ -374,3 +374,22 @@ pub(crate) async fn get_model_storage_directory_handler(
|
||||
.get_model_storage_directory()?;
|
||||
data_result_ok(LocalModelStoragePB { file_path })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn get_offline_app_handler(
|
||||
chat_manager: AFPluginState<Weak<ChatManager>>,
|
||||
) -> DataResult<OfflineAIPB, FlowyError> {
|
||||
let chat_manager = upgrade_chat_manager(chat_manager)?;
|
||||
let (tx, rx) = oneshot::channel::<Result<String, FlowyError>>();
|
||||
tokio::spawn(async move {
|
||||
let link = chat_manager
|
||||
.local_ai_controller
|
||||
.get_offline_ai_app_download_link()
|
||||
.await?;
|
||||
let _ = tx.send(Ok(link));
|
||||
Ok::<_, FlowyError>(())
|
||||
});
|
||||
|
||||
let link = rx.await??;
|
||||
data_result_ok(OfflineAIPB { link })
|
||||
}
|
||||
@@ -57,6 +57,7 @@ pub fn init(chat_manager: Weak<ChatManager>) -> AFPlugin {
|
||||
ChatEvent::GetModelStorageDirectory,
|
||||
get_model_storage_directory_handler,
|
||||
)
|
||||
.event(ChatEvent::GetOfflineAIAppLink, get_offline_app_handler)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||
@@ -133,4 +134,7 @@ pub enum ChatEvent {
|
||||
|
||||
#[event(output = "LocalModelStoragePB")]
|
||||
GetModelStorageDirectory = 21,
|
||||
|
||||
#[event(output = "OfflineAIPB")]
|
||||
GetOfflineAIAppLink = 22,
|
||||
}
|
||||
Loaded 100 of 107 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user