diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart index 7b69ed818e..b6a50d9661 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar/app_bar.dart @@ -37,6 +37,7 @@ class FlowyAppBar extends AppBar { Widget? title, String? titleText, FlowyAppBarLeadingType leadingType = FlowyAppBarLeadingType.back, + Widget? leading, super.centerTitle, VoidCallback? onTapLeading, bool showDivider = true, @@ -50,7 +51,7 @@ class FlowyAppBar extends AppBar { ), titleSpacing: 0, elevation: 0, - leading: leadingType.getWidget(onTapLeading), + leading: leading ?? leadingType.getWidget(onTapLeading), leadingWidth: leadingType.width, toolbarHeight: 44.0, bottom: showDivider diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart index 58c8244547..57be077b01 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart @@ -16,6 +16,7 @@ import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -50,8 +51,14 @@ class _MobileViewPageState extends State { // used to determine if the user has scrolled down and show the app bar in immersive mode ScrollNotificationObserverState? _scrollNotificationObserver; + + // control the app bar opacity when in immersive mode final ValueNotifier _appBarOpacity = ValueNotifier(0.0); + // only enable immersive mode for document layout + final ValueNotifier _isImmersiveMode = ValueNotifier(false); + ViewListener? viewListener; + @override void initState() { super.initState(); @@ -61,6 +68,8 @@ class _MobileViewPageState extends State { @override void dispose() { _appBarOpacity.dispose(); + _isImmersiveMode.dispose(); + viewListener?.stop(); _scrollNotificationObserver = null; super.dispose(); } @@ -87,6 +96,12 @@ class _MobileViewPageState extends State { } else { body = state.data!.fold((view) { viewPB = view; + _updateImmersiveMode(view); + viewListener?.stop(); + viewListener = ViewListener(viewId: view.id) + ..start( + onViewUpdated: _updateImmersiveMode, + ); actions.addAll([ if (FeatureFlag.syncDocument.isOn) ...[ @@ -195,6 +210,15 @@ class _MobileViewPageState extends State { AppBarTheme.of(context).backgroundColor?.withOpacity(opacity), showDivider: false, title: Opacity(opacity: opacity >= 0.99 ? 1.0 : 0, child: title), + leading: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 2.0, vertical: 4.0), + child: AppBarButton( + padding: EdgeInsets.zero, + onTap: (context) => context.pop(), + child: _buildImmersiveAppBarIcon(FlowySvgs.m_app_bar_back_s), + ), + ), actions: actions, ), ), @@ -230,7 +254,7 @@ class _MobileViewPageState extends State { } return AppBarButton( - padding: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(vertical: 2.0), onTap: (context) { EditorNotification.exitEditing().post(); @@ -250,13 +274,13 @@ class _MobileViewPageState extends State { ), ); }, - child: const FlowySvg(FlowySvgs.m_layout_s), + child: _buildImmersiveAppBarIcon(FlowySvgs.m_layout_s), ); } Widget _buildAppBarMoreButton(ViewPB view) { return AppBarButton( - padding: const EdgeInsets.only(left: 8, right: 16), + padding: const EdgeInsets.only(left: 8, right: 16, top: 2, bottom: 2), onTap: (context) { EditorNotification.exitEditing().post(); @@ -268,7 +292,49 @@ class _MobileViewPageState extends State { builder: (_) => _buildAppBarMoreBottomSheet(context), ); }, - child: const FlowySvg(FlowySvgs.m_app_bar_more_s), + child: _buildImmersiveAppBarIcon(FlowySvgs.m_app_bar_more_s), + ); + } + + Widget _buildImmersiveAppBarIcon(FlowySvgData icon) { + return ValueListenableBuilder( + valueListenable: _isImmersiveMode, + builder: (context, isImmersiveMode, child) { + return ValueListenableBuilder( + valueListenable: _appBarOpacity, + builder: (context, appBarOpacity, child) { + Color? color; + + // if there's no cover or the cover is not immersive, + // make sure the app bar is always visible + if (!isImmersiveMode) { + color = null; + } else if (appBarOpacity < 0.99) { + color = Colors.white; + } + + Widget child = Container( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + child: FlowySvg( + icon, + color: color, + ), + ); + + if (isImmersiveMode && appBarOpacity <= 0.99) { + child = DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(22), + color: Colors.black.withOpacity(0.2), + ), + child: child, + ); + } + + return child; + }, + ); + }, ); } @@ -331,6 +397,7 @@ class _MobileViewPageState extends State { if (_scrollNotificationObserver == null) { return; } + if (notification is ScrollUpdateNotification && defaultScrollNotificationPredicate(notification)) { final ScrollMetrics metrics = notification.metrics; @@ -344,4 +411,16 @@ class _MobileViewPageState extends State { } } } + + void _updateImmersiveMode(ViewPB view) { + final cover = view.cover; + if (cover == null || cover.type == PageStyleCoverImageType.none) { + _isImmersiveMode.value = false; + } else if (view.layout != ViewLayoutPB.Document) { + // only support immersive mode for document layout + _isImmersiveMode.value = false; + } else { + _isImmersiveMode.value = true; + } + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart index 217d8781ff..3fe480b2a8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart @@ -103,13 +103,16 @@ class _DocumentImmersiveCoverState extends State { _buildIcon(context, icon), const HSpace(8.0), ], - Expanded(child: _buildTitle(context)), + Expanded(child: _buildTitle(context, state)), ], ), ); } - Widget _buildTitle(BuildContext context) { + Widget _buildTitle( + BuildContext context, + DocumentImmersiveCoverState state, + ) { String? fontFamily = builtInFontFamily(); final documentFontFamily = context.read().state.fontFamily; @@ -131,6 +134,9 @@ class _DocumentImmersiveCoverState extends State { fontSize: 28.0, fontWeight: FontWeight.w700, fontFamily: fontFamily, + color: state.cover.type == PageStyleCoverImageType.none + ? null + : Colors.white, ), onSubmitted: (value) { scrollController.position.jumpTo(0);