diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart index 4d9869e349..c34b0abcfe 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart @@ -71,7 +71,6 @@ class AIChatPagePlugin extends Plugin { void dispose() { _viewInfoBloc.close(); notifier.dispose(); - super.dispose(); } } @@ -119,4 +118,7 @@ class AIChatPagePluginWidgetBuilder extends PluginWidgetBuilder @override List get navigationItems => [this]; + + @override + EdgeInsets get contentPadding => EdgeInsets.zero; } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart index 1a7393a0a9..c6a5e832c3 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.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/ai_message_bubble.dart'; @@ -10,12 +13,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.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:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; -import 'package:flutter_chat_ui/flutter_chat_ui.dart' show Chat; 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_popmenu.dart'; @@ -27,11 +28,11 @@ import 'presentation/message/user_text_message.dart'; class AIChatUILayout { static EdgeInsets get chatPadding => - isMobile ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 70); + isMobile ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 20); static EdgeInsets get welcomePagePadding => isMobile ? const EdgeInsets.symmetric(horizontal: 20) - : const EdgeInsets.symmetric(horizontal: 100); + : const EdgeInsets.symmetric(horizontal: 50); static double get messageWidthRatio => 0.85; @@ -44,7 +45,8 @@ class AIChatUILayout { query.padding.right, query.viewInsets.bottom + query.padding.bottom, ) - : const EdgeInsets.symmetric(horizontal: 70); + : const EdgeInsets.symmetric(horizontal: 50) + + const EdgeInsets.only(bottom: 20); } } @@ -77,92 +79,89 @@ class _AIChatPageState extends State { Widget build(BuildContext context) { if (widget.userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) { return buildChatWidget(); - } else { - return Center( - child: FlowyText( - LocaleKeys.chat_unsupportedCloudPrompt.tr(), - fontSize: 20, - ), - ); } + + return Center( + child: FlowyText( + LocaleKeys.chat_unsupportedCloudPrompt.tr(), + fontSize: 20, + ), + ); } Widget buildChatWidget() { - return SizedBox.expand( - child: Padding( - padding: AIChatUILayout.chatPadding, - child: BlocProvider( - create: (context) => ChatBloc( - view: widget.view, - userProfile: widget.userProfile, - )..add(const ChatEvent.initialLoad()), - child: BlocBuilder( - builder: (blocContext, state) { - return Chat( - messages: state.messages, - onAttachmentPressed: () {}, - onSendPressed: (types.PartialText message) { - // We use custom bottom widget for chat input, so - // do not need to handle this event. - }, - customBottomWidget: buildChatInput(blocContext), - user: _user, - theme: buildTheme(context), - onEndReached: () async { - if (state.hasMorePrevMessage && - state.loadingPreviousStatus != - const LoadingState.loading()) { - blocContext - .read() - .add(const ChatEvent.startLoadingPrevMessage()); - } - }, - emptyState: BlocBuilder( - builder: (context, state) { - return state.initialLoadingStatus == + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 784), + child: BlocProvider( + create: (_) => ChatBloc( + view: widget.view, + userProfile: widget.userProfile, + )..add(const ChatEvent.initialLoad()), + child: BlocBuilder( + builder: (blocContext, state) => Chat( + messages: state.messages, + onSendPressed: (_) { + // We use custom bottom widget for chat input, so + // do not need to handle this event. + }, + customBottomWidget: buildChatInput(blocContext), + user: _user, + theme: buildTheme(context), + onEndReached: () async { + if (state.hasMorePrevMessage && + state.loadingPreviousStatus != + const LoadingState.loading()) { + blocContext + .read() + .add(const ChatEvent.startLoadingPrevMessage()); + } + }, + emptyState: BlocBuilder( + builder: (_, state) => state.initialLoadingStatus == const LoadingState.finish() ? Padding( padding: AIChatUILayout.welcomePagePadding, child: ChatWelcomePage( - onSelectedQuestion: (question) { - blocContext - .read() - .add(ChatEvent.sendMessage(question)); - }, + onSelectedQuestion: (question) => blocContext + .read() + .add(ChatEvent.sendMessage(question)), ), ) : const Center( child: CircularProgressIndicator.adaptive(), - ); + ), + ), + messageWidthRatio: AIChatUILayout.messageWidthRatio, + textMessageBuilder: ( + textMessage, { + required messageWidth, + required showName, + }) => + _buildAITextMessage(blocContext, textMessage), + bubbleBuilder: ( + child, { + required message, + required nextMessageInGroup, + }) { + if (message.author.id == _user.id) { + return ChatUserMessageBubble( + message: message, + child: child, + ); + } + + return _buildAIBubble(message, blocContext, state, child); }, ), - messageWidthRatio: AIChatUILayout.messageWidthRatio, - textMessageBuilder: ( - textMessage, { - required messageWidth, - required showName, - }) { - return _buildAITextMessage(blocContext, textMessage); - }, - bubbleBuilder: ( - child, { - required message, - required nextMessageInGroup, - }) { - if (message.author.id == _user.id) { - return ChatUserMessageBubble( - message: message, - child: child, - ); - } else { - return _buildAIBubble(message, blocContext, state, child); - } - }, - ); - }, + ), + ), ), ), - ), + ], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart index 5aceca2025..48c69eef97 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart @@ -1,9 +1,10 @@ +import 'package:flutter/material.dart'; + 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: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'; @@ -31,19 +32,15 @@ class ChatWelcomePage extends StatelessWidget { size: Size.square(44), ), const SizedBox(height: 40), - GridView.builder( - shrinkWrap: true, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isMobile ? 2 : 4, - crossAxisSpacing: 6, - mainAxisSpacing: 6, - childAspectRatio: 16.0 / 9.0, - ), - itemCount: items.length, - itemBuilder: (context, index) => WelcomeQuestion( - question: items[index], - onSelected: onSelectedQuestion, - ), + Wrap( + children: items + .map( + (i) => WelcomeQuestion( + question: i, + onSelected: onSelectedQuestion, + ), + ) + .toList(), ), ], ), diff --git a/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart index 4e900f91d2..e1ca000939 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart @@ -70,7 +70,6 @@ class UserWorkspaceBloc extends Bloc { final workspaceMemberResult = await _userService.getWorkspaceMember(); final workspaceMember = workspaceMemberResult.toNullable(); - emit(state.copyWith(currentWorkspaceMember: workspaceMember)); }, fetchWorkspaces: () async { @@ -510,6 +509,7 @@ class UserWorkspaceState with _$UserWorkspaceState { if (identical(this, other)) return true; return other is UserWorkspaceState && + other.currentWorkspaceMember == currentWorkspaceMember && other.currentWorkspace == currentWorkspace && _deepCollectionEquality.equals(other.workspaces, workspaces) && identical(other.actionResult, actionResult); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart index 3f5f0057a3..a47d9db862 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart @@ -1,5 +1,7 @@ import 'dart:async'; +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/blank/blank.dart'; @@ -34,7 +36,6 @@ import 'package:easy_localization/easy_localization.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/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; /// Home Sidebar is the left side bar of the home page. diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_ai_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_ai_view.dart index d5f4a62558..a047afff0f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_ai_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_ai_view.dart @@ -10,7 +10,6 @@ import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget { @@ -31,30 +30,25 @@ class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget { } class SettingsAIView extends StatelessWidget { - const SettingsAIView({ - super.key, - required this.userProfile, - }); + const SettingsAIView({super.key, required this.userProfile}); final UserProfilePB userProfile; @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => + create: (_) => SettingsAIBloc(userProfile)..add(const SettingsAIEvent.started()), child: BlocBuilder( - builder: (context, state) { - return SettingsBody( - title: LocaleKeys.settings_aiPage_title.tr(), - description: - LocaleKeys.settings_aiPage_keys_aiSettingsDescription.tr(), - children: const [ - AIModelSeclection(), - _AISearchToggle(value: false), - ], - ); - }, + builder: (_, __) => SettingsBody( + title: LocaleKeys.settings_aiPage_title.tr(), + description: + LocaleKeys.settings_aiPage_keys_aiSettingsDescription.tr(), + children: const [ + AIModelSeclection(), + _AISearchToggle(value: false), + ], + ), ), ); } @@ -66,23 +60,22 @@ class AIModelSeclection extends StatelessWidget { @override Widget build(BuildContext context) { return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - FlowyText( - LocaleKeys.settings_aiPage_keys_llmModel.tr(), - fontSize: 14, + Flexible( + child: FlowyText.medium( + LocaleKeys.settings_aiPage_keys_llmModel.tr(), + ), ), const Spacer(), - BlocBuilder( - builder: (context, state) { - return Expanded( - child: SettingsDropdown( + Flexible( + child: BlocBuilder( + builder: (context, state) { + return SettingsDropdown( key: const Key('AIModelDropdown'), - expandWidth: false, - onChanged: (format) { - context.read().add( - SettingsAIEvent.selectModel(format), - ); - }, + onChanged: (model) => context + .read() + .add(SettingsAIEvent.selectModel(model)), selectedOption: state.userProfile.aiModel, options: _availableModels .map( @@ -93,9 +86,9 @@ class AIModelSeclection extends StatelessWidget { ), ) .toList(), - ), - ); - }, + ); + }, + ), ), ], ); @@ -139,17 +132,21 @@ class _AISearchToggle extends StatelessWidget { Widget build(BuildContext context) { return Row( children: [ - Expanded( - child: FlowyText.regular( - LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(), - fontSize: 16, - ), + FlowyText.medium( + LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(), ), - const HSpace(16), + const Spacer(), BlocBuilder( builder: (context, state) { if (state.aiSettings == null) { - return const CircularProgressIndicator.adaptive(); + return const Padding( + padding: EdgeInsets.only(top: 6), + child: SizedBox( + height: 26, + width: 26, + child: CircularProgressIndicator.adaptive(), + ), + ); } else { return Toggle( value: state.enableSearchIndexing, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart index 96f3cde646..33c4f3ab0f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart @@ -494,6 +494,7 @@ class _ActionButton extends StatelessWidget { ? SystemMouseCursors.click : MouseCursor.defer, child: _drawBorder( + context, isLM: isLM, isUpgrade: isUpgrade, child: Container( @@ -544,7 +545,8 @@ class _ActionButton extends StatelessWidget { ); } - Widget _drawBorder({ + Widget _drawBorder( + BuildContext context, { required bool isLM, required bool isUpgrade, required Widget child, @@ -562,7 +564,7 @@ class _ActionButton extends StatelessWidget { ], ) : null, - border: isUpgrade ? null : Border.all(), + border: isUpgrade ? null : Border.all(color: const Color(0xFF333333)), borderRadius: BorderRadius.circular(16), ), child: child, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart index 3c67d6c4f3..edbb698f92 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart @@ -70,6 +70,7 @@ class SettingsPlanView extends StatelessWidget { usage: state.workspaceUsage, subscription: state.subscription, ), + const VSpace(16), _CurrentPlanBox(subscription: state.subscription), ], ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart index 27b0596d3a..972e818b55 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart @@ -490,14 +490,11 @@ class KeyBadge extends StatelessWidget { ), child: Center( child: iconData != null - ? FlowySvg( - iconData!, - color: AFThemeExtension.of(context).strongText, - ) + ? FlowySvg(iconData!, color: Colors.black) : FlowyText.medium( keyLabel.toLowerCase(), fontSize: 12, - color: AFThemeExtension.of(context).strongText, + color: Colors.black, ), ), ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart index 5cce796f8d..c105d1bb4d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart @@ -24,6 +24,7 @@ import 'package:appflowy/workspace/presentation/settings/shared/setting_list_til import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart'; +import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_dashed_divider.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_input_field.dart'; @@ -85,6 +86,7 @@ class SettingsWorkspaceView extends StatelessWidget { return SettingsBody( title: LocaleKeys.settings_workspacePage_title.tr(), description: LocaleKeys.settings_workspacePage_description.tr(), + autoSeparate: false, children: [ // We don't allow changing workspace name/icon for local/offline if (userProfile.authenticator != AuthenticatorPB.Local) ...[ @@ -93,6 +95,7 @@ class SettingsWorkspaceView extends StatelessWidget { .tr(), children: [_WorkspaceNameSetting(member: workspaceMember)], ), + const SettingsCategorySpacer(), SettingsCategory( title: LocaleKeys.settings_workspacePage_workspaceIcon_title .tr(), @@ -106,11 +109,14 @@ class SettingsWorkspaceView extends StatelessWidget { ), ], ), + const SettingsCategorySpacer(), ], SettingsCategory( title: LocaleKeys.settings_workspacePage_appearance_title.tr(), children: const [AppearanceSelector()], ), + const VSpace(16), + // const SettingsCategorySpacer(), SettingsCategory( title: LocaleKeys.settings_workspacePage_theme_title.tr(), description: @@ -121,6 +127,7 @@ class SettingsWorkspaceView extends StatelessWidget { _DocumentSelectionColorSetting(), ], ), + const SettingsCategorySpacer(), SettingsCategory( title: LocaleKeys.settings_workspacePage_workspaceFont_title.tr(), @@ -129,7 +136,9 @@ class SettingsWorkspaceView extends StatelessWidget { currentFont: context.read().state.font, ), - const SettingsDashedDivider(), + SettingsDashedDivider( + color: Theme.of(context).colorScheme.outline, + ), SettingsCategory( title: LocaleKeys.settings_workspacePage_textDirection_title .tr(), @@ -140,11 +149,14 @@ class SettingsWorkspaceView extends StatelessWidget { ), ], ), + const VSpace(16), SettingsCategory( title: LocaleKeys.settings_workspacePage_layoutDirection_title .tr(), children: const [_LayoutDirectionSelect()], ), + const SettingsCategorySpacer(), + SettingsCategory( title: LocaleKeys.settings_workspacePage_dateTime_title.tr(), children: [ @@ -156,10 +168,14 @@ class SettingsWorkspaceView extends StatelessWidget { const _DateFormatDropdown(), ], ), + const SettingsCategorySpacer(), + SettingsCategory( title: LocaleKeys.settings_workspacePage_language_title.tr(), children: const [LanguageDropdown()], ), + const SettingsCategorySpacer(), + if (userProfile.authenticator != AuthenticatorPB.Local) ...[ SingleSettingAction( label: LocaleKeys.settings_workspacePage_manageWorkspace_title diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart index e464963ef8..de7f167a45 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart @@ -1,4 +1,3 @@ -import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:flutter/material.dart'; import 'package:appflowy/startup/startup.dart'; @@ -15,6 +14,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/f import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_view.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart index 041822947f..8091a72684 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart @@ -33,7 +33,7 @@ class SettingsBody extends StatelessWidget { mainAxisSize: MainAxisSize.min, separatorBuilder: () => autoSeparate ? const SettingsCategorySpacer() - : const VSpace(16), + : const SizedBox.shrink(), crossAxisAlignment: CrossAxisAlignment.start, children: children, ),