diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart index 43f008bcb497..1dc28541765d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart @@ -39,7 +39,11 @@ import 'package:universal_platform/universal_platform.dart'; import '../../../document/presentation/editor_plugins/plugins.dart'; -const _coverHeight = 250.0; +/// We have the cover height as public as it is used in the row_detail.dart file +/// Used to determine the position of the row actions depending on if there is a cover or not. +/// +const rowCoverHeight = 250.0; + const _iconHeight = 60.0; const _toolbarHeight = 40.0; @@ -181,11 +185,11 @@ class _RowBannerState extends State { double _calculateOverallHeight(bool hasIcon, bool hasCover) { switch ((hasIcon, hasCover)) { case (true, true): - return _coverHeight + _toolbarHeight; + return rowCoverHeight + _toolbarHeight; case (true, false): return 50 + _iconHeight + _toolbarHeight; case (false, true): - return _coverHeight + _toolbarHeight; + return rowCoverHeight + _toolbarHeight; case (false, false): return _toolbarHeight; } @@ -224,7 +228,7 @@ class _RowCoverState extends State { @override Widget build(BuildContext context) { return SizedBox( - height: _coverHeight, + height: rowCoverHeight, child: MouseRegion( onEnter: (_) => setState(() => isOverlayButtonsHidden = false), onExit: (_) => setState(() => isOverlayButtonsHidden = true), @@ -364,7 +368,7 @@ class _DesktopRowCoverState extends State { Widget build(BuildContext context) { if (cover.coverType == CoverTypePB.FileCover) { return SizedBox( - height: _coverHeight, + height: rowCoverHeight, width: double.infinity, child: AFImage( url: cover.data, @@ -376,7 +380,7 @@ class _DesktopRowCoverState extends State { if (cover.coverType == CoverTypePB.AssetCover) { return SizedBox( - height: _coverHeight, + height: rowCoverHeight, width: double.infinity, child: Image.asset( PageStyleCoverImageType.builtInImagePath(cover.data), @@ -389,7 +393,7 @@ class _DesktopRowCoverState extends State { final color = FlowyTint.fromId(cover.data)?.color(context) ?? cover.data.tryToColor(); return Container( - height: _coverHeight, + height: rowCoverHeight, width: double.infinity, color: color, ); @@ -397,7 +401,7 @@ class _DesktopRowCoverState extends State { if (cover.coverType == CoverTypePB.GradientCover) { return Container( - height: _coverHeight, + height: rowCoverHeight, width: double.infinity, decoration: BoxDecoration( gradient: FlowyGradientColor.fromId(cover.data).linear, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart index 5a47f23d85f0..f4e981a8b8e5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; @@ -41,13 +42,26 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate { } class _RowDetailPageState extends State { - final scrollController = ScrollController(); late final cellBuilder = EditableCellBuilder( databaseController: widget.databaseController, ); + late final ScrollController scrollController; + + double scrollOffset = 0; + + @override + void initState() { + super.initState(); + scrollController = ScrollController( + onAttach: (_) => attachScrollListener(), + ); + } + + void attachScrollListener() => scrollController.addListener(onScrollChanged); @override void dispose() { + scrollController.removeListener(onScrollChanged); scrollController.dispose(); super.dispose(); } @@ -65,53 +79,78 @@ class _RowDetailPageState extends State { ), BlocProvider.value(value: getIt()), ], - child: Stack( - children: [ - ListView( - controller: scrollController, - children: [ - RowBanner( - databaseController: widget.databaseController, - rowController: widget.rowController, - cellBuilder: cellBuilder, - allowOpenAsFullPage: widget.allowOpenAsFullPage, - userProfile: widget.userProfile, - ), - const VSpace(16), - Padding( - padding: const EdgeInsets.only(left: 40, right: 60), - child: RowPropertyList( + child: BlocBuilder( + builder: (context, state) => Stack( + children: [ + ListView( + controller: scrollController, + physics: const ClampingScrollPhysics(), + children: [ + RowBanner( + databaseController: widget.databaseController, + rowController: widget.rowController, cellBuilder: cellBuilder, - viewId: widget.databaseController.viewId, - fieldController: widget.databaseController.fieldController, + allowOpenAsFullPage: widget.allowOpenAsFullPage, + userProfile: widget.userProfile, ), + const VSpace(16), + Padding( + padding: const EdgeInsets.only(left: 40, right: 60), + child: RowPropertyList( + cellBuilder: cellBuilder, + viewId: widget.databaseController.viewId, + fieldController: + widget.databaseController.fieldController, + ), + ), + const VSpace(20), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 60), + child: Divider(height: 1.0), + ), + const VSpace(20), + RowDocument( + viewId: widget.rowController.viewId, + rowId: widget.rowController.rowId, + ), + ], + ), + Positioned( + top: calculateActionsOffset( + state.rowMeta.cover.data.isNotEmpty, ), - const VSpace(20), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 60), - child: Divider(height: 1.0), - ), - const VSpace(20), - RowDocument( - viewId: widget.rowController.viewId, - rowId: widget.rowController.rowId, + right: 12, + child: Row( + children: actions(context), ), - ], - ), - Positioned( - top: 12, - right: 12, - child: Row( - children: _actions(context), ), - ), - ], + ], + ), ), ), ); } - List _actions(BuildContext context) { + void onScrollChanged() { + if (scrollOffset != scrollController.offset) { + setState(() => scrollOffset = scrollController.offset); + } + } + + double calculateActionsOffset(bool hasCover) { + if (!hasCover) { + return 12; + } + + final offsetByScroll = clampDouble( + rowCoverHeight - scrollOffset, + 0, + rowCoverHeight, + ); + return 12 + offsetByScroll; + } + + List actions(BuildContext context) { return [ if (widget.allowOpenAsFullPage) ...[ FlowyTooltip(