feat: add ai bubble button on mobile home page (#5992)

* chore: skip check list test if the task is not found

* feat: add ai bubble button in home page

* feat: only show the ai bubble button for the cloud user

* chore: add border color to ai bubble button

* Revert "chore: skip check list test if the task is not found"

This reverts commit 961f594a31.

* fix: only display ai bubble button on home page
This commit is contained in:
Lucas.Xu 2024-08-19 09:50:42 +08:00 committed by GitHub
parent d0ce65f711
commit e460120a1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 197 additions and 75 deletions

View File

@ -134,75 +134,7 @@ class _MobileHomePageState extends State<MobileHomePage> {
value: getIt<ReminderBloc>()..add(const ReminderEvent.started()), value: getIt<ReminderBloc>()..add(const ReminderEvent.started()),
), ),
], ],
child: BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>( child: _HomePage(userProfile: widget.userProfile),
buildWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
listener: (context, state) {
getIt<CachedRecentService>().reset();
mCurrentWorkspace.value = state.currentWorkspace;
},
builder: (context, state) {
if (state.currentWorkspace == null) {
return const SizedBox.shrink();
}
final workspaceId = state.currentWorkspace!.workspaceId;
return Column(
children: [
// Header
Padding(
padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: 8.0,
top: Platform.isAndroid ? 8.0 : 0.0,
),
child: MobileHomePageHeader(
userProfile: widget.userProfile,
),
),
Expanded(
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => SpaceOrderBloc()
..add(const SpaceOrderEvent.initial()),
),
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(
SidebarSectionsEvent.initial(
widget.userProfile,
workspaceId,
),
),
),
BlocProvider(
create: (_) =>
FavoriteBloc()..add(const FavoriteEvent.initial()),
),
BlocProvider(
create: (_) => SpaceBloc()
..add(
SpaceEvent.initial(
widget.userProfile,
workspaceId,
openFirstPage: false,
),
),
),
],
child: MobileSpaceTab(
userProfile: widget.userProfile,
),
),
),
],
);
},
),
); );
} }
@ -214,3 +146,82 @@ class _MobileHomePageState extends State<MobileHomePage> {
await FolderEventSetLatestView(ViewIdPB(value: id)).send(); await FolderEventSetLatestView(ViewIdPB(value: id)).send();
} }
} }
class _HomePage extends StatelessWidget {
const _HomePage({required this.userProfile});
final UserProfilePB userProfile;
@override
Widget build(BuildContext context) {
return BlocConsumer<UserWorkspaceBloc, UserWorkspaceState>(
buildWhen: (previous, current) =>
previous.currentWorkspace?.workspaceId !=
current.currentWorkspace?.workspaceId,
listener: (context, state) {
getIt<CachedRecentService>().reset();
mCurrentWorkspace.value = state.currentWorkspace;
},
builder: (context, state) {
if (state.currentWorkspace == null) {
return const SizedBox.shrink();
}
final workspaceId = state.currentWorkspace!.workspaceId;
return Column(
children: [
// Header
Padding(
padding: EdgeInsets.only(
left: HomeSpaceViewSizes.mHorizontalPadding,
right: 8.0,
top: Platform.isAndroid ? 8.0 : 0.0,
),
child: MobileHomePageHeader(
userProfile: userProfile,
),
),
Expanded(
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (_) =>
SpaceOrderBloc()..add(const SpaceOrderEvent.initial()),
),
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(
SidebarSectionsEvent.initial(
userProfile,
workspaceId,
),
),
),
BlocProvider(
create: (_) =>
FavoriteBloc()..add(const FavoriteEvent.initial()),
),
BlocProvider(
create: (_) => SpaceBloc()
..add(
SpaceEvent.initial(
userProfile,
workspaceId,
openFirstPage: false,
),
),
),
],
child: MobileSpaceTab(
userProfile: userProfile,
),
),
),
],
);
},
);
}
}

View File

@ -0,0 +1,81 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/gesture.dart';
import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart';
import 'package:appflowy/util/theme_extension.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class FloatingAIEntry extends StatelessWidget {
const FloatingAIEntry({super.key});
@override
Widget build(BuildContext context) {
return AnimatedGestureDetector(
scaleFactor: 0.99,
onTapUp: () => mobileCreateNewAIChatNotifier.value =
mobileCreateNewAIChatNotifier.value + 1,
child: DecoratedBox(
decoration: _buildShadowDecoration(context),
child: Container(
decoration: _buildWrapperDecoration(context),
height: 48,
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 18),
child: _buildHintText(context),
),
),
),
);
}
BoxDecoration _buildShadowDecoration(BuildContext context) {
return BoxDecoration(
borderRadius: BorderRadius.circular(30),
boxShadow: [
BoxShadow(
blurRadius: 20,
spreadRadius: 1,
offset: const Offset(0, 4),
color: Colors.black.withOpacity(0.05),
),
],
);
}
BoxDecoration _buildWrapperDecoration(BuildContext context) {
final outlineColor = Theme.of(context).colorScheme.outline;
final borderColor = Theme.of(context).isLightMode
? outlineColor.withOpacity(0.7)
: outlineColor.withOpacity(0.3);
return BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Theme.of(context).colorScheme.surface,
border: Border.fromBorderSide(
BorderSide(
color: borderColor,
),
),
);
}
Widget _buildHintText(BuildContext context) {
return Row(
children: [
FlowySvg(
FlowySvgs.toolbar_item_ai_s,
size: const Size.square(16.0),
color: Theme.of(context).hintColor,
opacity: 0.7,
),
const HSpace(8),
FlowyText(
LocaleKeys.chat_inputMessageHint.tr(),
color: Theme.of(context).hintColor,
),
],
);
}
}

View File

@ -20,6 +20,10 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'ai_bubble_button.dart';
final ValueNotifier<int> mobileCreateNewAIChatNotifier = ValueNotifier(0);
class MobileSpaceTab extends StatefulWidget { class MobileSpaceTab extends StatefulWidget {
const MobileSpaceTab({ const MobileSpaceTab({
super.key, super.key,
@ -40,7 +44,8 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
void initState() { void initState() {
super.initState(); super.initState();
mobileCreateNewPageNotifier.addListener(_createNewPage); mobileCreateNewPageNotifier.addListener(_createNewDocument);
mobileCreateNewAIChatNotifier.addListener(_createNewAIChat);
mobileLeaveWorkspaceNotifier.addListener(_leaveWorkspace); mobileLeaveWorkspaceNotifier.addListener(_leaveWorkspace);
} }
@ -48,7 +53,9 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
void dispose() { void dispose() {
tabController?.removeListener(_onTabChange); tabController?.removeListener(_onTabChange);
tabController?.dispose(); tabController?.dispose();
mobileCreateNewPageNotifier.removeListener(_createNewPage);
mobileCreateNewPageNotifier.removeListener(_createNewDocument);
mobileCreateNewAIChatNotifier.removeListener(_createNewAIChat);
mobileLeaveWorkspaceNotifier.removeListener(_leaveWorkspace); mobileLeaveWorkspaceNotifier.removeListener(_leaveWorkspace);
super.dispose(); super.dispose();
@ -145,7 +152,20 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
case MobileSpaceTabType.recent: case MobileSpaceTabType.recent:
return const MobileRecentSpace(); return const MobileRecentSpace();
case MobileSpaceTabType.spaces: case MobileSpaceTabType.spaces:
return MobileHomeSpace(userProfile: widget.userProfile); return Stack(
children: [
MobileHomeSpace(userProfile: widget.userProfile),
// only show ai chat button for cloud user
if (widget.userProfile.authenticator ==
AuthenticatorPB.AppFlowyCloud)
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 16,
left: 20,
right: 20,
child: const FloatingAIEntry(),
),
],
);
case MobileSpaceTabType.favorites: case MobileSpaceTabType.favorites:
return MobileFavoriteSpace(userProfile: widget.userProfile); return MobileFavoriteSpace(userProfile: widget.userProfile);
default: default:
@ -155,15 +175,24 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
} }
// quick create new page when clicking the add button in navigation bar // quick create new page when clicking the add button in navigation bar
void _createNewPage() { void _createNewDocument() {
_createNewPage(ViewLayoutPB.Document);
}
void _createNewAIChat() {
_createNewPage(ViewLayoutPB.Chat);
}
void _createNewPage(ViewLayoutPB layout) {
if (context.read<SpaceBloc>().state.spaces.isNotEmpty) { if (context.read<SpaceBloc>().state.spaces.isNotEmpty) {
context.read<SpaceBloc>().add( context.read<SpaceBloc>().add(
SpaceEvent.createPage( SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: ViewLayoutPB.Document, layout: layout,
), ),
); );
} else { } else if (layout == ViewLayoutPB.Document) {
// only support create document in section
context.read<SidebarSectionsBloc>().add( context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.createRootViewInSection( SidebarSectionsEvent.createRootViewInSection(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(), name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),

View File

@ -73,6 +73,7 @@ class ChatWelcomePage extends StatelessWidget {
const VSpace(8), const VSpace(8),
Wrap( Wrap(
direction: Axis.vertical, direction: Axis.vertical,
spacing: isMobile ? 12.0 : 0.0,
children: items children: items
.map( .map(
(i) => WelcomeQuestionWidget( (i) => WelcomeQuestionWidget(