mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge branch 'main' into update-client-api
This commit is contained in:
commit
af1769164a
@ -175,7 +175,7 @@ SPEC CHECKSUMS:
|
|||||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
||||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||||
|
@ -23,6 +23,9 @@ class DocumentPageStyleBloc
|
|||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
try {
|
try {
|
||||||
|
if (view.id.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final layoutObject =
|
final layoutObject =
|
||||||
await ViewBackendService.getView(view.id).fold(
|
await ViewBackendService.getView(view.id).fold(
|
||||||
(s) => jsonDecode(s.extra),
|
(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.vibrate.call();
|
||||||
|
|
||||||
|
widget.onTapUp();
|
||||||
|
},
|
||||||
|
onTapDown: (details) {
|
||||||
|
setState(() => scale = widget.scaleFactor);
|
||||||
|
},
|
||||||
|
child: AnimatedScale(
|
||||||
|
scale: scale,
|
||||||
|
alignment: widget.alignment,
|
||||||
|
duration: widget.duration,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -24,9 +24,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
|||||||
height: 52.0,
|
height: 52.0,
|
||||||
leftIcon: const FlowySvg(
|
leftIcon: const FlowySvg(
|
||||||
FlowySvgs.icon_document_s,
|
FlowySvgs.icon_document_s,
|
||||||
size: Size.square(18),
|
size: Size.square(20),
|
||||||
),
|
),
|
||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
|
showBottomBorder: false,
|
||||||
onTap: () => onAction(ViewLayoutPB.Document),
|
onTap: () => onAction(ViewLayoutPB.Document),
|
||||||
),
|
),
|
||||||
FlowyOptionTile.text(
|
FlowyOptionTile.text(
|
||||||
@ -34,9 +35,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
|||||||
height: 52.0,
|
height: 52.0,
|
||||||
leftIcon: const FlowySvg(
|
leftIcon: const FlowySvg(
|
||||||
FlowySvgs.icon_grid_s,
|
FlowySvgs.icon_grid_s,
|
||||||
size: Size.square(18),
|
size: Size.square(20),
|
||||||
),
|
),
|
||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
|
showBottomBorder: false,
|
||||||
onTap: () => onAction(ViewLayoutPB.Grid),
|
onTap: () => onAction(ViewLayoutPB.Grid),
|
||||||
),
|
),
|
||||||
FlowyOptionTile.text(
|
FlowyOptionTile.text(
|
||||||
@ -44,9 +46,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
|||||||
height: 52.0,
|
height: 52.0,
|
||||||
leftIcon: const FlowySvg(
|
leftIcon: const FlowySvg(
|
||||||
FlowySvgs.icon_board_s,
|
FlowySvgs.icon_board_s,
|
||||||
size: Size.square(18),
|
size: Size.square(20),
|
||||||
),
|
),
|
||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
|
showBottomBorder: false,
|
||||||
onTap: () => onAction(ViewLayoutPB.Board),
|
onTap: () => onAction(ViewLayoutPB.Board),
|
||||||
),
|
),
|
||||||
FlowyOptionTile.text(
|
FlowyOptionTile.text(
|
||||||
@ -54,9 +57,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
|||||||
height: 52.0,
|
height: 52.0,
|
||||||
leftIcon: const FlowySvg(
|
leftIcon: const FlowySvg(
|
||||||
FlowySvgs.icon_calendar_s,
|
FlowySvgs.icon_calendar_s,
|
||||||
size: Size.square(18),
|
size: Size.square(20),
|
||||||
),
|
),
|
||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
|
showBottomBorder: false,
|
||||||
onTap: () => onAction(ViewLayoutPB.Calendar),
|
onTap: () => onAction(ViewLayoutPB.Calendar),
|
||||||
),
|
),
|
||||||
FlowyOptionTile.text(
|
FlowyOptionTile.text(
|
||||||
@ -64,9 +68,10 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget {
|
|||||||
height: 52.0,
|
height: 52.0,
|
||||||
leftIcon: const FlowySvg(
|
leftIcon: const FlowySvg(
|
||||||
FlowySvgs.chat_ai_page_s,
|
FlowySvgs.chat_ai_page_s,
|
||||||
size: Size.square(18),
|
size: Size.square(20),
|
||||||
),
|
),
|
||||||
showTopBorder: false,
|
showTopBorder: false,
|
||||||
|
showBottomBorder: false,
|
||||||
onTap: () => onAction(ViewLayoutPB.Chat),
|
onTap: () => onAction(ViewLayoutPB.Chat),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.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/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/recent/recent_views_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/application/view/view_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -109,16 +109,6 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
|||||||
await _showConfirmDialog(
|
await _showConfirmDialog(
|
||||||
onDelete: () {
|
onDelete: () {
|
||||||
recentViewsBloc.add(RecentViewsEvent.removeRecentViews([viewId]));
|
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) {
|
onRightButtonPressed: (context) {
|
||||||
onDelete();
|
onDelete();
|
||||||
|
|
||||||
Navigator.pop(context);
|
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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/recent/recent_views_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_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/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:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -40,18 +41,32 @@ enum MobilePaneActionType {
|
|||||||
backgroundColor: const Color(0xFFFA217F),
|
backgroundColor: const Color(0xFFFA217F),
|
||||||
svg: FlowySvgs.favorite_section_remove_from_favorite_s,
|
svg: FlowySvgs.favorite_section_remove_from_favorite_s,
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
onPressed: (context) => context
|
onPressed: (context) {
|
||||||
.read<FavoriteBloc>()
|
showToastNotification(
|
||||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
|
context,
|
||||||
|
message: LocaleKeys.button_unfavoriteSuccessfully.tr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context
|
||||||
|
.read<FavoriteBloc>()
|
||||||
|
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
case MobilePaneActionType.addToFavorites:
|
case MobilePaneActionType.addToFavorites:
|
||||||
return MobileSlideActionButton(
|
return MobileSlideActionButton(
|
||||||
backgroundColor: const Color(0xFF00C8FF),
|
backgroundColor: const Color(0xFF00C8FF),
|
||||||
svg: FlowySvgs.favorite_s,
|
svg: FlowySvgs.favorite_s,
|
||||||
size: 24.0,
|
size: 24.0,
|
||||||
onPressed: (context) => context
|
onPressed: (context) {
|
||||||
.read<FavoriteBloc>()
|
showToastNotification(
|
||||||
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view)),
|
context,
|
||||||
|
message: LocaleKeys.button_favoriteSuccessfully.tr(),
|
||||||
|
);
|
||||||
|
|
||||||
|
context
|
||||||
|
.read<FavoriteBloc>()
|
||||||
|
.add(FavoriteEvent.toggle(context.read<ViewBloc>().view));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
case MobilePaneActionType.add:
|
case MobilePaneActionType.add:
|
||||||
return MobileSlideActionButton(
|
return MobileSlideActionButton(
|
||||||
@ -69,6 +84,7 @@ enum MobilePaneActionType {
|
|||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
|
showDivider: false,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
builder: (sheetContext) {
|
builder: (sheetContext) {
|
||||||
return AddNewPageWidgetBottomSheet(
|
return AddNewPageWidgetBottomSheet(
|
||||||
@ -145,8 +161,6 @@ enum MobilePaneActionType {
|
|||||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||||
MobileViewItemBottomSheetBodyAction.divider,
|
MobileViewItemBottomSheetBodyAction.divider,
|
||||||
if (view.layout != ViewLayoutPB.Chat)
|
|
||||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
|
||||||
MobileViewItemBottomSheetBodyAction.divider,
|
MobileViewItemBottomSheetBodyAction.divider,
|
||||||
MobileViewItemBottomSheetBodyAction.removeFromRecent,
|
MobileViewItemBottomSheetBodyAction.removeFromRecent,
|
||||||
];
|
];
|
||||||
@ -156,7 +170,6 @@ enum MobilePaneActionType {
|
|||||||
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
? MobileViewItemBottomSheetBodyAction.removeFromFavorites
|
||||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||||
MobileViewItemBottomSheetBodyAction.divider,
|
MobileViewItemBottomSheetBodyAction.divider,
|
||||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,12 +194,13 @@ ActionPane buildEndActionPane(
|
|||||||
bool needSpace = true,
|
bool needSpace = true,
|
||||||
MobilePageCardType? cardType,
|
MobilePageCardType? cardType,
|
||||||
FolderSpaceType? spaceType,
|
FolderSpaceType? spaceType,
|
||||||
|
required double spaceRatio,
|
||||||
}) {
|
}) {
|
||||||
return ActionPane(
|
return ActionPane(
|
||||||
motion: const ScrollMotion(),
|
motion: const ScrollMotion(),
|
||||||
extentRatio: actions.length / 5,
|
extentRatio: actions.length / spaceRatio,
|
||||||
children: [
|
children: [
|
||||||
if (needSpace) const HSpace(20),
|
if (needSpace) const HSpace(60),
|
||||||
...actions.map(
|
...actions.map(
|
||||||
(action) => action.actionButton(
|
(action) => action.actionButton(
|
||||||
context,
|
context,
|
||||||
|
@ -70,6 +70,7 @@ Future<T?> showMobileBottomSheet<T>(
|
|||||||
backgroundColor ??= Theme.of(context).brightness == Brightness.light
|
backgroundColor ??= Theme.of(context).brightness == Brightness.light
|
||||||
? const Color(0xFFF7F8FB)
|
? const Color(0xFFF7F8FB)
|
||||||
: const Color(0xFF23262B);
|
: const Color(0xFF23262B);
|
||||||
|
barrierColor ??= Colors.black.withOpacity(0.3);
|
||||||
|
|
||||||
return showModalBottomSheet<T>(
|
return showModalBottomSheet<T>(
|
||||||
context: context,
|
context: context,
|
||||||
@ -226,10 +227,14 @@ class BottomSheetHeader extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
child: FlowyText(
|
child: Container(
|
||||||
title,
|
constraints: const BoxConstraints(maxWidth: 250),
|
||||||
fontSize: 16.0,
|
child: FlowyText(
|
||||||
fontWeight: FontWeight.w500,
|
title,
|
||||||
|
fontSize: 17.0,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showDoneButton)
|
if (showDoneButton)
|
||||||
|
@ -100,8 +100,6 @@ class _FavoriteViews extends StatelessWidget {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
key: const PageStorageKey('favorite_views_page_storage_key'),
|
key: const PageStorageKey('favorite_views_page_storage_key'),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
|
||||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
|
||||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||||
MediaQuery.of(context).padding.bottom,
|
MediaQuery.of(context).padding.bottom,
|
||||||
),
|
),
|
||||||
|
@ -72,6 +72,7 @@ class MobileFavoriteFolder extends StatelessWidget {
|
|||||||
MobilePaneActionType.more,
|
MobilePaneActionType.more,
|
||||||
],
|
],
|
||||||
spaceType: FolderSpaceType.favorite,
|
spaceType: FolderSpaceType.favorite,
|
||||||
|
spaceRatio: 5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -29,8 +29,6 @@ class _MobileHomeSpaceState extends State<MobileHomeSpace>
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
|
||||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
|
||||||
top: HomeSpaceViewSizes.mVerticalPadding,
|
top: HomeSpaceViewSizes.mVerticalPadding,
|
||||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||||
MediaQuery.of(context).padding.bottom,
|
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/folder/folder_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
import 'package:appflowy/workspace/application/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:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -91,7 +92,12 @@ class MobileFolders extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
..._buildSpaceOrSection(context, state),
|
..._buildSpaceOrSection(context, state),
|
||||||
const VSpace(4.0),
|
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/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.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/bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
|
import 'package:appflowy/mobile/presentation/home/mobile_home_setting_page.dart';
|
||||||
import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart';
|
import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart';
|
||||||
@ -113,8 +114,9 @@ class _MobileWorkspace extends StatelessWidget {
|
|||||||
if (currentWorkspace == null) {
|
if (currentWorkspace == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return GestureDetector(
|
return AnimatedGestureDetector(
|
||||||
onTap: () {
|
alignment: Alignment.centerLeft,
|
||||||
|
onTapUp: () {
|
||||||
context.read<UserWorkspaceBloc>().add(
|
context.read<UserWorkspaceBloc>().add(
|
||||||
const UserWorkspaceEvent.fetchWorkspaces(),
|
const UserWorkspaceEvent.fetchWorkspaces(),
|
||||||
);
|
);
|
||||||
@ -143,7 +145,7 @@ class _MobileWorkspace extends StatelessWidget {
|
|||||||
: const HSpace(8),
|
: const HSpace(8),
|
||||||
FlowyText.semibold(
|
FlowyText.semibold(
|
||||||
currentWorkspace.name,
|
currentWorkspace.name,
|
||||||
fontSize: 16.0,
|
fontSize: 20.0,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -162,9 +164,10 @@ class _MobileWorkspace extends StatelessWidget {
|
|||||||
showHeader: true,
|
showHeader: true,
|
||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
|
useRootNavigator: true,
|
||||||
title: LocaleKeys.workspace_menuTitle.tr(),
|
title: LocaleKeys.workspace_menuTitle.tr(),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
builder: (_) {
|
builder: (sheetContext) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: context.read<UserWorkspaceBloc>(),
|
value: context.read<UserWorkspaceBloc>(),
|
||||||
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||||
@ -179,7 +182,7 @@ class _MobileWorkspace extends StatelessWidget {
|
|||||||
currentWorkspace: currentWorkspace,
|
currentWorkspace: currentWorkspace,
|
||||||
workspaces: workspaces,
|
workspaces: workspaces,
|
||||||
onWorkspaceSelected: (workspace) {
|
onWorkspaceSelected: (workspace) {
|
||||||
context.pop();
|
Navigator.of(sheetContext).pop();
|
||||||
|
|
||||||
if (workspace == currentWorkspace) {
|
if (workspace == currentWorkspace) {
|
||||||
return;
|
return;
|
||||||
|
@ -72,8 +72,6 @@ class _RecentViews extends StatelessWidget {
|
|||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
key: const PageStorageKey('recent_views_page_storage_key'),
|
key: const PageStorageKey('recent_views_page_storage_key'),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: HomeSpaceViewSizes.mHorizontalPadding,
|
|
||||||
right: HomeSpaceViewSizes.mHorizontalPadding,
|
|
||||||
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
bottom: HomeSpaceViewSizes.mVerticalPadding +
|
||||||
MediaQuery.of(context).padding.bottom,
|
MediaQuery.of(context).padding.bottom,
|
||||||
),
|
),
|
||||||
|
@ -82,6 +82,7 @@ class MobileSectionFolder extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
spaceType: spaceType,
|
spaceType: spaceType,
|
||||||
needSpace: false,
|
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/mobile_router.dart';
|
||||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.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/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/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/shared/appflowy_network_image.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/date_format_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/date_time/time_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/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-folder/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
@ -76,13 +78,14 @@ class MobileViewPage extends StatelessWidget {
|
|||||||
: MobilePaneActionType.addToFavorites,
|
: MobilePaneActionType.addToFavorites,
|
||||||
],
|
],
|
||||||
cardType: type,
|
cardType: type,
|
||||||
|
spaceRatio: 4,
|
||||||
),
|
),
|
||||||
child: GestureDetector(
|
child: AnimatedGestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
onTapUp: () => context.pushView(view),
|
||||||
onTapUp: (_) => context.pushView(view),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
|
||||||
Expanded(child: _buildDescription(context, state)),
|
Expanded(child: _buildDescription(context, state)),
|
||||||
const HSpace(20.0),
|
const HSpace(20.0),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@ -90,6 +93,7 @@ class MobileViewPage extends StatelessWidget {
|
|||||||
height: 60,
|
height: 60,
|
||||||
child: _buildCover(context, state),
|
child: _buildCover(context, state),
|
||||||
),
|
),
|
||||||
|
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -211,7 +215,7 @@ class MobileViewPage extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildLastViewed(BuildContext context) {
|
Widget _buildLastViewed(BuildContext context) {
|
||||||
final textColor = Theme.of(context).isLightMode
|
final textColor = Theme.of(context).isLightMode
|
||||||
? const Color(0xFF171717)
|
? const Color(0x7F171717)
|
||||||
: Colors.white.withOpacity(0.45);
|
: Colors.white.withOpacity(0.45);
|
||||||
if (timestamp == null) {
|
if (timestamp == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
|
@ -50,23 +50,17 @@ class _MobileSpaceState extends State<MobileSpace> {
|
|||||||
MobileSpaceHeader(
|
MobileSpaceHeader(
|
||||||
isExpanded: state.isExpanded,
|
isExpanded: state.isExpanded,
|
||||||
space: currentSpace,
|
space: currentSpace,
|
||||||
onAdded: () {
|
onAdded: () => _showCreatePageMenu(currentSpace),
|
||||||
context.read<SpaceBloc>().add(
|
|
||||||
SpaceEvent.createPage(
|
|
||||||
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
|
||||||
layout: ViewLayoutPB.Document,
|
|
||||||
index: 0,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
context.read<SpaceBloc>().add(
|
|
||||||
SpaceEvent.expand(currentSpace, true),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onPressed: () => _showSpaceMenu(context),
|
onPressed: () => _showSpaceMenu(context),
|
||||||
),
|
),
|
||||||
_Pages(
|
Padding(
|
||||||
key: ValueKey(currentSpace.id),
|
padding: const EdgeInsets.only(
|
||||||
space: currentSpace,
|
left: HomeSpaceViewSizes.mHorizontalPadding,
|
||||||
|
),
|
||||||
|
child: _Pages(
|
||||||
|
key: ValueKey(currentSpace.id),
|
||||||
|
space: currentSpace,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -82,6 +76,7 @@ class _MobileSpaceState extends State<MobileSpace> {
|
|||||||
showDragHandle: true,
|
showDragHandle: true,
|
||||||
showCloseButton: true,
|
showCloseButton: true,
|
||||||
showDoneButton: true,
|
showDoneButton: true,
|
||||||
|
useRootNavigator: true,
|
||||||
title: LocaleKeys.space_title.tr(),
|
title: LocaleKeys.space_title.tr(),
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
builder: (_) {
|
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 {
|
class _Pages extends StatelessWidget {
|
||||||
@ -148,7 +175,7 @@ class _Pages extends StatelessWidget {
|
|||||||
MobilePaneActionType.add,
|
MobilePaneActionType.add,
|
||||||
],
|
],
|
||||||
spaceType: spaceType,
|
spaceType: spaceType,
|
||||||
needSpace: false,
|
spaceRatio: 4,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
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/workspace/presentation/home/menu/sidebar/space/space_icon.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -30,6 +31,7 @@ class MobileSpaceHeader extends StatelessWidget {
|
|||||||
height: 48,
|
height: 48,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
const HSpace(HomeSpaceViewSizes.mHorizontalPadding),
|
||||||
SpaceIcon(
|
SpaceIcon(
|
||||||
dimension: 24,
|
dimension: 24,
|
||||||
space: space,
|
space: space,
|
||||||
@ -49,8 +51,15 @@ class MobileSpaceHeader extends StatelessWidget {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: onAdded,
|
onTap: onAdded,
|
||||||
child: const FlowySvg(
|
child: Container(
|
||||||
FlowySvgs.m_space_add_s,
|
// 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: [
|
children: [
|
||||||
FlowyText.medium(
|
FlowyText.medium(
|
||||||
space.name,
|
space.name,
|
||||||
|
fontSize: 16.0,
|
||||||
),
|
),
|
||||||
const HSpace(6.0),
|
const HSpace(6.0),
|
||||||
if (space.spacePermission == SpacePermission.private)
|
if (space.spacePermission == SpacePermission.private)
|
||||||
@ -68,16 +69,17 @@ class _SidebarSpaceMenuItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
iconPadding: 10,
|
iconPadding: 10,
|
||||||
leftIcon: SpaceIcon(
|
leftIcon: SpaceIcon(
|
||||||
dimension: 24,
|
dimension: 24,
|
||||||
space: space,
|
space: space,
|
||||||
cornerRadius: 6.0,
|
cornerRadius: 6.0,
|
||||||
),
|
),
|
||||||
leftIconSize: const Size.square(20),
|
leftIconSize: const Size.square(24),
|
||||||
rightIcon: isSelected
|
rightIcon: isSelected
|
||||||
? const FlowySvg(
|
? const FlowySvg(
|
||||||
FlowySvgs.workspace_selected_s,
|
FlowySvgs.m_blue_check_s,
|
||||||
blendMode: null,
|
blendMode: null,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
@ -138,17 +138,20 @@ class _WorkspaceMenuItem extends StatelessWidget {
|
|||||||
height: 60,
|
height: 60,
|
||||||
showTopBorder: showTopBorder,
|
showTopBorder: showTopBorder,
|
||||||
showBottomBorder: false,
|
showBottomBorder: false,
|
||||||
leftIcon: WorkspaceIcon(
|
leftIcon: Padding(
|
||||||
enableEdit: false,
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
iconSize: 26,
|
child: WorkspaceIcon(
|
||||||
fontSize: 16.0,
|
enableEdit: false,
|
||||||
workspace: workspace,
|
iconSize: 26,
|
||||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
fontSize: 16.0,
|
||||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
workspace: workspace,
|
||||||
workspace.workspaceId,
|
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||||
result.emoji,
|
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||||
|
workspace.workspaceId,
|
||||||
|
result.emoji,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: workspace.workspaceId == currentWorkspace.workspaceId
|
trailing: workspace.workspaceId == currentWorkspace.workspaceId
|
||||||
? const FlowySvg(
|
? const FlowySvg(
|
||||||
|
@ -50,6 +50,9 @@ class MobileBottomNavigationBar extends StatelessWidget {
|
|||||||
final backgroundColor = isLightMode
|
final backgroundColor = isLightMode
|
||||||
? Colors.white.withOpacity(0.95)
|
? Colors.white.withOpacity(0.95)
|
||||||
: const Color(0xFF23262B).withOpacity(0.95);
|
: const Color(0xFF23262B).withOpacity(0.95);
|
||||||
|
final borderColor = isLightMode
|
||||||
|
? const Color(0x141F2329)
|
||||||
|
: const Color(0xFF23262B).withOpacity(0.5);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: navigationShell,
|
body: navigationShell,
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
@ -62,9 +65,7 @@ class MobileBottomNavigationBar extends StatelessWidget {
|
|||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: isLightMode
|
border: isLightMode
|
||||||
? Border(
|
? Border(top: BorderSide(color: borderColor))
|
||||||
top: BorderSide(color: Theme.of(context).dividerColor),
|
|
||||||
)
|
|
||||||
: null,
|
: null,
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
),
|
),
|
||||||
|
@ -300,7 +300,7 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
|
|||||||
)
|
)
|
||||||
: Opacity(
|
: Opacity(
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
child: widget.view.defaultIcon(),
|
child: widget.view.defaultIcon(size: const Size.square(18)),
|
||||||
);
|
);
|
||||||
return SizedBox(width: 18.0, child: icon);
|
return SizedBox(width: 18.0, child: icon);
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,9 @@ class ChatInputBloc extends Bloc<ChatInputEvent, ChatInputState> {
|
|||||||
: listener = LocalLLMListener(),
|
: listener = LocalLLMListener(),
|
||||||
super(const ChatInputState(aiType: _AppFlowyAI())) {
|
super(const ChatInputState(aiType: _AppFlowyAI())) {
|
||||||
listener.start(
|
listener.start(
|
||||||
stateCallback: (pluginState) {
|
chatStateCallback: (aiState) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(ChatInputEvent.updateState(pluginState));
|
add(ChatInputEvent.updateState(aiState));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -37,18 +37,26 @@ class ChatInputBloc extends Bloc<ChatInputEvent, ChatInputState> {
|
|||||||
) async {
|
) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
started: () async {
|
started: () async {
|
||||||
final result = await ChatEventGetLocalAIPluginState().send();
|
final result = await ChatEventGetLocalAIChatState().send();
|
||||||
result.fold(
|
result.fold(
|
||||||
(pluginState) {
|
(aiState) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(ChatInputEvent.updateState(pluginState));
|
add(
|
||||||
|
ChatInputEvent.updateState(aiState),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(err) => Log.error(err.toString()),
|
(err) {
|
||||||
|
Log.error(err.toString());
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
updateState: (LocalAIPluginStatePB aiPluginState) {
|
updateState: (aiState) {
|
||||||
emit(const ChatInputState(aiType: _AppFlowyAI()));
|
if (aiState.enabled) {
|
||||||
|
emit(const ChatInputState(aiType: _LocalAI()));
|
||||||
|
} else {
|
||||||
|
emit(const ChatInputState(aiType: _AppFlowyAI()));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -57,8 +65,8 @@ class ChatInputBloc extends Bloc<ChatInputEvent, ChatInputState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class ChatInputEvent with _$ChatInputEvent {
|
class ChatInputEvent with _$ChatInputEvent {
|
||||||
const factory ChatInputEvent.started() = _Started;
|
const factory ChatInputEvent.started() = _Started;
|
||||||
const factory ChatInputEvent.updateState(LocalAIPluginStatePB aiPluginState) =
|
const factory ChatInputEvent.updateState(LocalAIChatPB aiState) =
|
||||||
_UpdatePluginState;
|
_UpdateAIState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@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_types/flutter_chat_types.dart' as types;
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart' show Chat;
|
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_popmenu.dart';
|
||||||
import 'presentation/chat_theme.dart';
|
import 'presentation/chat_theme.dart';
|
||||||
import 'presentation/chat_user_invalid_message.dart';
|
import 'presentation/chat_user_invalid_message.dart';
|
||||||
@ -82,7 +82,9 @@ class AIChatPage extends StatelessWidget {
|
|||||||
userProfile: userProfile,
|
userProfile: userProfile,
|
||||||
)..add(const ChatEvent.initialLoad()),
|
)..add(const ChatEvent.initialLoad()),
|
||||||
),
|
),
|
||||||
BlocProvider(create: (_) => ChatInputBloc()),
|
BlocProvider(
|
||||||
|
create: (_) => ChatInputBloc()..add(const ChatInputEvent.started()),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: BlocListener<ChatFileBloc, ChatFileState>(
|
child: BlocListener<ChatFileBloc, ChatFileState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
@ -391,36 +393,39 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
|||||||
padding: AIChatUILayout.safeAreaInsets(context),
|
padding: AIChatUILayout.safeAreaInsets(context),
|
||||||
child: BlocBuilder<ChatInputBloc, ChatInputState>(
|
child: BlocBuilder<ChatInputBloc, ChatInputState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.aiType.when(
|
final hintText = state.aiType.when(
|
||||||
appflowyAI: () => Column(
|
appflowyAI: () => LocaleKeys.chat_inputMessageHint.tr(),
|
||||||
children: [
|
localAI: () => LocaleKeys.chat_inputLocalAIMessageHint.tr(),
|
||||||
BlocSelector<ChatBloc, ChatState, LoadingState>(
|
);
|
||||||
selector: (state) => state.streamingStatus,
|
|
||||||
builder: (context, state) {
|
return Column(
|
||||||
return ChatInput(
|
children: [
|
||||||
chatId: widget.view.id,
|
BlocSelector<ChatBloc, ChatState, LoadingState>(
|
||||||
onSendPressed: (message) =>
|
selector: (state) => state.streamingStatus,
|
||||||
onSendPressed(context, message.text),
|
builder: (context, state) {
|
||||||
isStreaming: state != const LoadingState.finish(),
|
return ChatInput(
|
||||||
onStopStreaming: () {
|
chatId: widget.view.id,
|
||||||
context
|
onSendPressed: (message) =>
|
||||||
.read<ChatBloc>()
|
onSendPressed(context, message.text),
|
||||||
.add(const ChatEvent.stopStream());
|
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/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.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_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/ai_chat/presentation/chat_popmenu.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,13 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/plugins/ai_chat/presentation/chat_inline_action_menu.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flowy_infra/theme_extension.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/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||||
|
|
||||||
|
import 'chat_accessory_button.dart';
|
||||||
|
|
||||||
class ChatInput extends StatefulWidget {
|
class ChatInput extends StatefulWidget {
|
||||||
/// Creates [ChatInput] widget.
|
/// Creates [ChatInput] widget.
|
||||||
const ChatInput({
|
const ChatInput({
|
||||||
@ -20,6 +19,7 @@ class ChatInput extends StatefulWidget {
|
|||||||
this.options = const InputOptions(),
|
this.options = const InputOptions(),
|
||||||
required this.isStreaming,
|
required this.isStreaming,
|
||||||
required this.onStopStreaming,
|
required this.onStopStreaming,
|
||||||
|
required this.hintText,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool? isAttachmentUploading;
|
final bool? isAttachmentUploading;
|
||||||
@ -29,6 +29,7 @@ class ChatInput extends StatefulWidget {
|
|||||||
final InputOptions options;
|
final InputOptions options;
|
||||||
final String chatId;
|
final String chatId;
|
||||||
final bool isStreaming;
|
final bool isStreaming;
|
||||||
|
final String hintText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatInput> createState() => _ChatInputState();
|
State<ChatInput> createState() => _ChatInputState();
|
||||||
@ -36,6 +37,11 @@ class ChatInput extends StatefulWidget {
|
|||||||
|
|
||||||
/// [ChatInput] widget state.
|
/// [ChatInput] widget state.
|
||||||
class _ChatInputState extends State<ChatInput> {
|
class _ChatInputState extends State<ChatInput> {
|
||||||
|
final GlobalKey _textFieldKey = GlobalKey();
|
||||||
|
final LayerLink _layerLink = LayerLink();
|
||||||
|
// final ChatTextFieldInterceptor _textFieldInterceptor =
|
||||||
|
// ChatTextFieldInterceptor();
|
||||||
|
|
||||||
late final _inputFocusNode = FocusNode(
|
late final _inputFocusNode = FocusNode(
|
||||||
onKeyEvent: (node, event) {
|
onKeyEvent: (node, event) {
|
||||||
if (event.physicalKey == PhysicalKeyboardKey.enter &&
|
if (event.physicalKey == PhysicalKeyboardKey.enter &&
|
||||||
@ -59,9 +65,9 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
late TextEditingController _textController;
|
||||||
|
|
||||||
bool _sendButtonVisible = false;
|
bool _sendButtonVisible = false;
|
||||||
late TextEditingController _textController;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -71,33 +77,15 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
_handleSendButtonVisibilityModeChange();
|
_handleSendButtonVisibilityModeChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSendButtonVisibilityModeChange() {
|
@override
|
||||||
_textController.removeListener(_handleTextControllerChange);
|
void dispose() {
|
||||||
_sendButtonVisible =
|
_inputFocusNode.dispose();
|
||||||
_textController.text.trim() != '' || widget.isStreaming;
|
_textController.dispose();
|
||||||
_textController.addListener(_handleTextControllerChange);
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSendPressed() {
|
@override
|
||||||
final trimmedText = _textController.text.trim();
|
Widget build(BuildContext context) {
|
||||||
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() {
|
|
||||||
const textPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 6);
|
const textPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 6);
|
||||||
const buttonPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 6);
|
const buttonPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 6);
|
||||||
const inputPadding = EdgeInsets.all(6);
|
const inputPadding = EdgeInsets.all(6);
|
||||||
@ -128,27 +116,78 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Padding _inputTextField(EdgeInsets textPadding) {
|
void _handleSendButtonVisibilityModeChange() {
|
||||||
return Padding(
|
_textController.removeListener(_handleTextControllerChange);
|
||||||
padding: textPadding,
|
_sendButtonVisible =
|
||||||
child: TextField(
|
_textController.text.trim() != '' || widget.isStreaming;
|
||||||
controller: _textController,
|
_textController.addListener(_handleTextControllerChange);
|
||||||
focusNode: _inputFocusNode,
|
}
|
||||||
decoration: InputDecoration(
|
|
||||||
border: InputBorder.none,
|
void _handleSendPressed() {
|
||||||
hintText: LocaleKeys.chat_inputMessageHint.tr(),
|
final trimmedText = _textController.text.trim();
|
||||||
hintStyle: TextStyle(
|
if (trimmedText != '') {
|
||||||
color: AFThemeExtension.of(context).textColor.withOpacity(0.5),
|
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,
|
visible: _sendButtonVisible,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: buttonPadding,
|
padding: buttonPadding,
|
||||||
child: AccessoryButton(
|
child: ChatInputAccessoryButton(
|
||||||
onSendPressed: () {
|
onSendPressed: () {
|
||||||
if (!widget.isStreaming) {
|
if (!widget.isStreaming) {
|
||||||
widget.onStopStreaming();
|
widget.onStopStreaming();
|
||||||
_handleSendPressed();
|
_handleSendPressed();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onStopStreaming: () {
|
onStopStreaming: () => widget.onStopStreaming(),
|
||||||
widget.onStopStreaming();
|
|
||||||
},
|
|
||||||
isStreaming: widget.isStreaming,
|
isStreaming: widget.isStreaming,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -184,64 +221,20 @@ class _ChatInputState extends State<ChatInput> {
|
|||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
_handleSendButtonVisibilityModeChange();
|
_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 ||
|
final isMobile = defaultTargetPlatform == TargetPlatform.android ||
|
||||||
defaultTargetPlatform == TargetPlatform.iOS;
|
defaultTargetPlatform == TargetPlatform.iOS;
|
||||||
|
|
||||||
class AccessoryButton extends StatelessWidget {
|
class ChatInputAnchor extends ChatAnchor {
|
||||||
const AccessoryButton({
|
ChatInputAnchor({
|
||||||
required this.onSendPressed,
|
required this.anchorKey,
|
||||||
required this.onStopStreaming,
|
required this.layerLink,
|
||||||
required this.isStreaming,
|
|
||||||
super.key,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final void Function() onSendPressed;
|
@override
|
||||||
final void Function() onStopStreaming;
|
final GlobalKey<State<StatefulWidget>> anchorKey;
|
||||||
final bool isStreaming;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
final LayerLink layerLink;
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import 'package:flowy_infra_ui/style_widget/hover.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'chat_input.dart';
|
import 'chat_input/chat_input.dart';
|
||||||
|
|
||||||
class ChatWelcomePage extends StatelessWidget {
|
class ChatWelcomePage extends StatelessWidget {
|
||||||
ChatWelcomePage({required this.onSelectedQuestion, super.key});
|
ChatWelcomePage({required this.onSelectedQuestion, super.key});
|
||||||
|
@ -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_configuration.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||||
import 'package:appflowy/util/theme_extension.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:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:markdown_widget/markdown_widget.dart';
|
import 'package:markdown_widget/markdown_widget.dart';
|
||||||
|
|
||||||
import 'selectable_highlight.dart';
|
import 'selectable_highlight.dart';
|
||||||
@ -30,7 +33,11 @@ class AIMarkdownText extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AIMarkdownType.appflowyEditor:
|
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:
|
case AIMarkdownType.markdownWidget:
|
||||||
return _ThirdPartyMarkdown(markdown: markdown);
|
return _ThirdPartyMarkdown(markdown: markdown);
|
||||||
}
|
}
|
||||||
|
@ -90,19 +90,22 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Stack(
|
return Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
_buildCover(context, state),
|
child: Stack(
|
||||||
Positioned(
|
children: [
|
||||||
left: 0,
|
_buildCover(context, state),
|
||||||
right: 0,
|
Positioned(
|
||||||
bottom: 0,
|
left: 0,
|
||||||
child: Padding(
|
right: 0,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
bottom: 0,
|
||||||
child: iconAndTitle,
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24.0),
|
||||||
|
child: iconAndTitle,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,6 @@ import 'package:appflowy/core/config/kv.dart';
|
|||||||
import 'package:appflowy/core/config/kv_keys.dart';
|
import 'package:appflowy/core/config/kv_keys.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
typedef FeatureFlagMap = Map<FeatureFlag, bool>;
|
typedef FeatureFlagMap = Map<FeatureFlag, bool>;
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/mobile/application/mobile_router.dart';
|
import 'package:appflowy/mobile/application/mobile_router.dart';
|
||||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||||
import 'package:appflowy/shared/feature_flags.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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:toastification/toastification.dart';
|
||||||
|
|
||||||
import 'prelude.dart';
|
import 'prelude.dart';
|
||||||
|
|
||||||
@ -197,31 +197,33 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
|
|||||||
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
_setSystemOverlayStyle(state);
|
_setSystemOverlayStyle(state);
|
||||||
return MaterialApp.router(
|
return ToastificationWrapper(
|
||||||
builder: (context, child) => MediaQuery(
|
child: MaterialApp.router(
|
||||||
// use the 1.0 as the textScaleFactor to avoid the text size
|
builder: (context, child) => MediaQuery(
|
||||||
// affected by the system setting.
|
// use the 1.0 as the textScaleFactor to avoid the text size
|
||||||
data: MediaQuery.of(context).copyWith(
|
// affected by the system setting.
|
||||||
textScaler: TextScaler.linear(state.textScaleFactor),
|
data: MediaQuery.of(context).copyWith(
|
||||||
),
|
textScaler: TextScaler.linear(state.textScaleFactor),
|
||||||
child: overlayManagerBuilder(
|
),
|
||||||
context,
|
child: overlayManagerBuilder(
|
||||||
!PlatformExtension.isMobile && FeatureFlag.search.isOn
|
context,
|
||||||
? CommandPalette(
|
!PlatformExtension.isMobile && FeatureFlag.search.isOn
|
||||||
notifier: _commandPaletteNotifier,
|
? CommandPalette(
|
||||||
child: child,
|
notifier: _commandPaletteNotifier,
|
||||||
)
|
child: 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,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -48,7 +48,7 @@ class ViewExtKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension ViewExtension on ViewPB {
|
extension ViewExtension on ViewPB {
|
||||||
Widget defaultIcon() => FlowySvg(
|
Widget defaultIcon({Size? size}) => FlowySvg(
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
ViewLayoutPB.Board => FlowySvgs.icon_board_s,
|
ViewLayoutPB.Board => FlowySvgs.icon_board_s,
|
||||||
ViewLayoutPB.Calendar => FlowySvgs.icon_calendar_s,
|
ViewLayoutPB.Calendar => FlowySvgs.icon_calendar_s,
|
||||||
@ -57,6 +57,7 @@ extension ViewExtension on ViewPB {
|
|||||||
ViewLayoutPB.Chat => FlowySvgs.chat_ai_page_s,
|
ViewLayoutPB.Chat => FlowySvgs.chat_ai_page_s,
|
||||||
_ => FlowySvgs.document_s,
|
_ => FlowySvgs.document_s,
|
||||||
},
|
},
|
||||||
|
size: size,
|
||||||
);
|
);
|
||||||
|
|
||||||
PluginType get pluginType => switch (layout) {
|
PluginType get pluginType => switch (layout) {
|
||||||
|
@ -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/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/tasks/app_widget.dart';
|
import 'package:appflowy/startup/tasks/app_widget.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
@ -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/buttons/secondary_button.dart';
|
||||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toastification/toastification.dart';
|
import 'package:toastification/toastification.dart';
|
||||||
|
|
||||||
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||||
@ -303,6 +304,18 @@ void showToastNotification(
|
|||||||
String? description,
|
String? description,
|
||||||
ToastificationType type = ToastificationType.success,
|
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(
|
toastification.show(
|
||||||
context: context,
|
context: context,
|
||||||
type: type,
|
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({
|
Future<void> showConfirmDeletionDialog({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String name,
|
required String name,
|
||||||
|
@ -53,8 +53,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "268aae9"
|
ref: aac7729
|
||||||
resolved-ref: "268aae905b18efc8a3a9c88dc75ebd19b314bd43"
|
resolved-ref: aac77292a1a175fd7450eef30167032d3cec7fea
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
@ -1670,10 +1670,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: qr
|
name: qr
|
||||||
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
|
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.2"
|
||||||
realtime_client:
|
realtime_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -2443,5 +2443,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.22.0"
|
flutter: ">=3.22.0"
|
||||||
|
@ -194,7 +194,7 @@ dependency_overrides:
|
|||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
ref: "268aae9"
|
ref: "aac7729"
|
||||||
|
|
||||||
appflowy_editor_plugins:
|
appflowy_editor_plugins:
|
||||||
git:
|
git:
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"newChat": "AI Chat",
|
"newChat": "AI Chat",
|
||||||
"inputMessageHint": "Message @:appName AI",
|
"inputMessageHint": "Message @:appName AI",
|
||||||
|
"inputLocalAIMessageHint": "Message @:appName Local AI",
|
||||||
"unsupportedCloudPrompt": "This feature is only available when using @:appName Cloud",
|
"unsupportedCloudPrompt": "This feature is only available when using @:appName Cloud",
|
||||||
"relatedQuestion": "Related",
|
"relatedQuestion": "Related",
|
||||||
"serverUnavailable": "Service Temporarily Unavailable. Please try again later.",
|
"serverUnavailable": "Service Temporarily Unavailable. Please try again later.",
|
||||||
@ -276,7 +277,7 @@
|
|||||||
"justNow": "just now",
|
"justNow": "just now",
|
||||||
"minutesAgo": "{count} minutes ago",
|
"minutesAgo": "{count} minutes ago",
|
||||||
"lastViewed": "Last viewed",
|
"lastViewed": "Last viewed",
|
||||||
"favoriteAt": "Favorited at",
|
"favoriteAt": "Favorited",
|
||||||
"emptyRecent": "No Recent Documents",
|
"emptyRecent": "No Recent Documents",
|
||||||
"emptyRecentDescription": "As you view documents, they will appear here for easy retrieval",
|
"emptyRecentDescription": "As you view documents, they will appear here for easy retrieval",
|
||||||
"emptyFavorite": "No Favorite Documents",
|
"emptyFavorite": "No Favorite Documents",
|
||||||
@ -337,6 +338,8 @@
|
|||||||
"removeFromFavorites": "Remove from favorites",
|
"removeFromFavorites": "Remove from favorites",
|
||||||
"removeFromRecent": "Remove from recent",
|
"removeFromRecent": "Remove from recent",
|
||||||
"addToFavorites": "Add to favorites",
|
"addToFavorites": "Add to favorites",
|
||||||
|
"favoriteSuccessfully": "Favorited success",
|
||||||
|
"unfavoriteSuccessfully": "Unfavorited success",
|
||||||
"rename": "Rename",
|
"rename": "Rename",
|
||||||
"helpCenter": "Help Center",
|
"helpCenter": "Help Center",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
Loading…
Reference in New Issue
Block a user