mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
remove bottom sheet
This commit is contained in:
@@ -16,13 +16,11 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.widget.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
@@ -103,14 +101,9 @@ class AssetViewer extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const double _kBottomSheetMinimumExtent = 0.4;
|
|
||||||
const double _kBottomSheetSnapExtent = 0.67;
|
|
||||||
|
|
||||||
class _AssetViewerState extends ConsumerState<AssetViewer> {
|
class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||||
static final _dummyListener = ImageStreamListener((image, _) => image.dispose());
|
static final _dummyListener = ImageStreamListener((image, _) => image.dispose());
|
||||||
late PageController pageController;
|
late PageController pageController;
|
||||||
late DraggableScrollableController bottomSheetController;
|
|
||||||
PersistentBottomSheetController? sheetCloseController;
|
|
||||||
// PhotoViewGallery takes care of disposing it's controllers
|
// PhotoViewGallery takes care of disposing it's controllers
|
||||||
PhotoViewControllerBase? viewController;
|
PhotoViewControllerBase? viewController;
|
||||||
StreamSubscription? reloadSubscription;
|
StreamSubscription? reloadSubscription;
|
||||||
@@ -123,7 +116,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
bool dragInProgress = false;
|
bool dragInProgress = false;
|
||||||
bool shouldPopOnDrag = false;
|
bool shouldPopOnDrag = false;
|
||||||
bool assetReloadRequested = false;
|
bool assetReloadRequested = false;
|
||||||
double previousExtent = _kBottomSheetMinimumExtent;
|
|
||||||
Offset dragDownPosition = Offset.zero;
|
Offset dragDownPosition = Offset.zero;
|
||||||
int totalAssets = 0;
|
int totalAssets = 0;
|
||||||
int stackIndex = 0;
|
int stackIndex = 0;
|
||||||
@@ -141,6 +133,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
double _assetDetailsOpacity = 0.0;
|
double _assetDetailsOpacity = 0.0;
|
||||||
|
|
||||||
|
final _assetDetailsSnap = 5;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -148,7 +142,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
pageController = PageController(initialPage: widget.initialIndex);
|
pageController = PageController(initialPage: widget.initialIndex);
|
||||||
_scrollController.addListener(_onScroll);
|
_scrollController.addListener(_onScroll);
|
||||||
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
||||||
bottomSheetController = DraggableScrollableController();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
WidgetsBinding.instance.addPostFrameCallback(_onAssetInit);
|
||||||
reloadSubscription = EventStream.shared.listen(_onEvent);
|
reloadSubscription = EventStream.shared.listen(_onEvent);
|
||||||
heroOffset = widget.heroOffset ?? TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
heroOffset = widget.heroOffset ?? TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
||||||
@@ -180,7 +173,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
pageController.dispose();
|
pageController.dispose();
|
||||||
bottomSheetController.dispose();
|
|
||||||
_cancelTimers();
|
_cancelTimers();
|
||||||
reloadSubscription?.cancel();
|
reloadSubscription?.cancel();
|
||||||
_prevPreCacheStream?.removeListener(_dummyListener);
|
_prevPreCacheStream?.removeListener(_dummyListener);
|
||||||
@@ -190,7 +182,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get showingBottomSheet => ref.read(assetViewerProvider.select((s) => s.showingBottomSheet));
|
bool get showingBottomSheet => _scrollController.offset > 0;
|
||||||
|
|
||||||
Color get backgroundColor {
|
Color get backgroundColor {
|
||||||
final opacity = ref.read(assetViewerProvider.select((s) => s.backgroundOpacity));
|
final opacity = ref.read(assetViewerProvider.select((s) => s.backgroundOpacity));
|
||||||
@@ -204,9 +196,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
_delayedOperations.clear();
|
_delayedOperations.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
double _getVerticalOffsetForBottomSheet(double extent) =>
|
|
||||||
(context.height * extent) - (context.height * _kBottomSheetMinimumExtent);
|
|
||||||
|
|
||||||
ImageStream _precacheImage(BaseAsset asset) {
|
ImageStream _precacheImage(BaseAsset asset) {
|
||||||
final provider = getFullImageProvider(asset, size: context.sizeData);
|
final provider = getFullImageProvider(asset, size: context.sizeData);
|
||||||
return provider.resolve(ImageConfiguration.empty)..addListener(_dummyListener);
|
return provider.resolve(ImageConfiguration.empty)..addListener(_dummyListener);
|
||||||
@@ -284,17 +273,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onPageBuild(PhotoViewControllerBase controller) {
|
|
||||||
viewController ??= controller;
|
|
||||||
if (showingBottomSheet && bottomSheetController.isAttached) {
|
|
||||||
final verticalOffset =
|
|
||||||
(context.height * bottomSheetController.size) - (context.height * _kBottomSheetMinimumExtent);
|
|
||||||
controller.position = Offset(0, -verticalOffset);
|
|
||||||
// Apply the zoom effect when the bottom sheet is showing
|
|
||||||
controller.scale = (controller.scale ?? 1.0) + 0.01;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onPageChanged(int index, PhotoViewControllerBase? controller) {
|
void _onPageChanged(int index, PhotoViewControllerBase? controller) {
|
||||||
_onAssetChanged(index);
|
_onAssetChanged(index);
|
||||||
viewController = controller;
|
viewController = controller;
|
||||||
@@ -328,7 +306,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
|
|
||||||
// Do not reset the state if the bottom sheet is showing
|
// Do not reset the state if the bottom sheet is showing
|
||||||
if (showingBottomSheet) {
|
if (showingBottomSheet) {
|
||||||
_snapBottomSheet();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,32 +334,14 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
final delta = details.localPosition - dragDownPosition;
|
final delta = details.localPosition - dragDownPosition;
|
||||||
hasDraggedDown ??= delta.dy > 0;
|
hasDraggedDown ??= delta.dy > 0;
|
||||||
if (!hasDraggedDown! || showingBottomSheet) {
|
if (!hasDraggedDown! || showingBottomSheet) {
|
||||||
_handleDragUp(ctx, delta);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleDragDown(ctx, delta);
|
_handleDragDown(ctx, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleDragUp(BuildContext ctx, Offset delta) {
|
|
||||||
const double openThreshold = 50;
|
|
||||||
|
|
||||||
final position = initialPhotoViewState.position + Offset(0, delta.dy);
|
|
||||||
final distanceToOrigin = position.distance;
|
|
||||||
|
|
||||||
viewController?.updateMultiple(position: position);
|
|
||||||
// Moves the bottom sheet when the asset is being dragged up
|
|
||||||
if (showingBottomSheet && bottomSheetController.isAttached) {
|
|
||||||
final centre = (ctx.height * _kBottomSheetMinimumExtent);
|
|
||||||
bottomSheetController.jumpTo((centre + distanceToOrigin) / ctx.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distanceToOrigin > openThreshold && !showingBottomSheet && !ref.read(readonlyModeProvider)) {
|
|
||||||
// _openBottomSheet(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDragDown(BuildContext ctx, Offset delta) {
|
void _handleDragDown(BuildContext ctx, Offset delta) {
|
||||||
|
print("drag down");
|
||||||
const double dragRatio = 0.2;
|
const double dragRatio = 0.2;
|
||||||
const double popThreshold = 75;
|
const double popThreshold = 75;
|
||||||
|
|
||||||
@@ -409,48 +368,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _onNotification(Notification delta) {
|
|
||||||
if (delta is DraggableScrollableNotification) {
|
|
||||||
_handleDraggableNotification(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle sheet snap manually so that the it snaps only at _kBottomSheetSnapExtent but not after
|
|
||||||
// the isSnapping guard is to prevent the notification from recursively handling the
|
|
||||||
// notification, eventually resulting in a heap overflow
|
|
||||||
if (!isSnapping && delta is ScrollEndNotification) {
|
|
||||||
_snapBottomSheet();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleDraggableNotification(DraggableScrollableNotification delta) {
|
|
||||||
final currentExtent = delta.extent;
|
|
||||||
final isDraggingDown = currentExtent < previousExtent;
|
|
||||||
previousExtent = currentExtent;
|
|
||||||
// Closes the bottom sheet if the user is dragging down
|
|
||||||
if (isDraggingDown && delta.extent < 0.67) {
|
|
||||||
if (dragInProgress) {
|
|
||||||
blockGestures = true;
|
|
||||||
}
|
|
||||||
// Jump to a lower position before starting close animation to prevent glitch
|
|
||||||
if (bottomSheetController.isAttached) {
|
|
||||||
bottomSheetController.jumpTo(0.67);
|
|
||||||
}
|
|
||||||
sheetCloseController?.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the asset is being dragged down, we do not want to update the asset position again
|
|
||||||
if (dragInProgress) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final verticalOffset = _getVerticalOffsetForBottomSheet(delta.extent);
|
|
||||||
// Moves the asset when the bottom sheet is being dragged
|
|
||||||
if (verticalOffset > 0) {
|
|
||||||
viewController?.position = Offset(0, -verticalOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onEvent(Event event) {
|
void _onEvent(Event event) {
|
||||||
if (event is TimelineReloadEvent) {
|
if (event is TimelineReloadEvent) {
|
||||||
_onTimelineReloadEvent();
|
_onTimelineReloadEvent();
|
||||||
@@ -461,14 +378,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
assetReloadRequested = true;
|
assetReloadRequested = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event is ViewerOpenBottomSheetEvent) {
|
|
||||||
final extent = _kBottomSheetMinimumExtent + 0.3;
|
|
||||||
_openBottomSheet(scaffoldContext!, extent: extent, activitiesMode: event.activitiesMode);
|
|
||||||
final offset = _getVerticalOffsetForBottomSheet(extent);
|
|
||||||
viewController?.position = Offset(0, -offset);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onTimelineReloadEvent() {
|
void _onTimelineReloadEvent() {
|
||||||
@@ -502,50 +411,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_onAssetChanged(pageController.page!.round());
|
_onAssetChanged(pageController.page!.round());
|
||||||
sheetCloseController?.close();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) {
|
|
||||||
ref.read(assetViewerProvider.notifier).setBottomSheet(true);
|
|
||||||
previousExtent = _kBottomSheetMinimumExtent;
|
|
||||||
sheetCloseController = showBottomSheet(
|
|
||||||
context: ctx,
|
|
||||||
sheetAnimationStyle: const AnimationStyle(duration: Durations.medium2, reverseDuration: Durations.medium2),
|
|
||||||
constraints: const BoxConstraints(maxWidth: double.infinity),
|
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20.0))),
|
|
||||||
backgroundColor: ctx.colorScheme.surfaceContainerLowest,
|
|
||||||
builder: (_) {
|
|
||||||
return NotificationListener<Notification>(
|
|
||||||
onNotification: _onNotification,
|
|
||||||
child: activitiesMode
|
|
||||||
? ActivitiesBottomSheet(controller: bottomSheetController, initialChildSize: extent)
|
|
||||||
: AssetDetailBottomSheet(controller: bottomSheetController, initialChildSize: extent),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
sheetCloseController?.closed.then((_) => _handleSheetClose());
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSheetClose() {
|
|
||||||
viewController?.animateMultiple(position: Offset.zero);
|
|
||||||
viewController?.updateMultiple(scale: viewController?.initialScale);
|
|
||||||
ref.read(assetViewerProvider.notifier).setBottomSheet(false);
|
|
||||||
sheetCloseController = null;
|
|
||||||
shouldPopOnDrag = false;
|
|
||||||
hasDraggedDown = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _snapBottomSheet() {
|
|
||||||
if (!bottomSheetController.isAttached ||
|
|
||||||
bottomSheetController.size > _kBottomSheetSnapExtent ||
|
|
||||||
bottomSheetController.size < 0.4) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isSnapping = true;
|
|
||||||
bottomSheetController.animateTo(_kBottomSheetSnapExtent, duration: Durations.short3, curve: Curves.easeOut);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _placeholderBuilder(BuildContext ctx, ImageChunkEvent? progress, int index) {
|
Widget _placeholderBuilder(BuildContext ctx, ImageChunkEvent? progress, int index) {
|
||||||
return const Center(child: ImmichLoadingIndicator());
|
return const Center(child: ImmichLoadingIndicator());
|
||||||
}
|
}
|
||||||
@@ -666,7 +534,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Rebuild the widget when the asset viewer state changes
|
// Rebuild the widget when the asset viewer state changes
|
||||||
// Using multiple selectors to avoid unnecessary rebuilds for other state changes
|
// Using multiple selectors to avoid unnecessary rebuilds for other state changes
|
||||||
ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet));
|
|
||||||
ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity));
|
ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity));
|
||||||
ref.watch(assetViewerProvider.select((s) => s.stackIndex));
|
ref.watch(assetViewerProvider.select((s) => s.stackIndex));
|
||||||
ref.watch(isPlayingMotionVideoProvider);
|
ref.watch(isPlayingMotionVideoProvider);
|
||||||
@@ -734,70 +601,54 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
final topPadding = math.max((viewportHeight - imageHeight) / 2, 0.0);
|
final topPadding = math.max((viewportHeight - imageHeight) / 2, 0.0);
|
||||||
final snapOffset = math.min(topPadding + (imageHeight / 2), (viewportHeight / 3) * 2);
|
final snapOffset = math.min(topPadding + (imageHeight / 2), (viewportHeight / 3) * 2);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return Stack(
|
||||||
controller: _scrollController,
|
clipBehavior: Clip.none,
|
||||||
physics: VariableHeightSnappingPhysics(snapStart: 0, snapEnd: snapOffset, snapOffset: snapOffset),
|
children: [
|
||||||
child: Column(
|
SingleChildScrollView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
controller: _scrollController,
|
||||||
children: [
|
physics: VariableHeightSnappingPhysics(snapStart: 0, snapEnd: snapOffset, snapOffset: snapOffset),
|
||||||
SizedBox(
|
child: Column(
|
||||||
height: imageHeight + topPadding,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
child: Stack(
|
children: [
|
||||||
clipBehavior: Clip.none,
|
SizedBox(height: topPadding),
|
||||||
children: [
|
SizedBox(
|
||||||
Column(
|
height: imageHeight,
|
||||||
children: [
|
child: PhotoViewGallery.builder(
|
||||||
SizedBox(height: topPadding),
|
gaplessPlayback: true,
|
||||||
SizedBox(
|
loadingBuilder: _placeholderBuilder,
|
||||||
height: imageHeight,
|
pageController: pageController,
|
||||||
child: PhotoViewGallery.builder(
|
scrollPhysics: CurrentPlatform.isIOS
|
||||||
gaplessPlayback: true,
|
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||||
loadingBuilder: _placeholderBuilder,
|
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
||||||
pageController: pageController,
|
itemCount: totalAssets,
|
||||||
scrollPhysics: CurrentPlatform.isIOS
|
onPageChanged: _onPageChanged,
|
||||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
scaleStateChangedCallback: _onScaleStateChanged,
|
||||||
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
builder: _assetBuilder,
|
||||||
itemCount: totalAssets,
|
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
||||||
onPageChanged: _onPageChanged,
|
enablePanAlways: true,
|
||||||
onPageBuild: _onPageBuild,
|
|
||||||
scaleStateChangedCallback: _onScaleStateChanged,
|
|
||||||
builder: _assetBuilder,
|
|
||||||
backgroundDecoration: BoxDecoration(color: backgroundColor),
|
|
||||||
enablePanAlways: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Positioned(
|
),
|
||||||
height: viewportHeight,
|
AnimatedOpacity(
|
||||||
top: 0,
|
opacity: _assetDetailsOpacity,
|
||||||
left: 0,
|
duration: kThemeAnimationDuration,
|
||||||
right: 0,
|
child: AssetDetails(minHeight: viewportHeight / 3 * 2),
|
||||||
child: const IgnorePointer(
|
),
|
||||||
// TODO: Sync with whether it's visible?
|
],
|
||||||
// TODO: Hide on scroll
|
|
||||||
ignoring: true,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [AssetStackRow(), ViewerBottomBar()],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
AnimatedOpacity(
|
),
|
||||||
opacity: _assetDetailsOpacity,
|
Positioned(
|
||||||
duration: kThemeAnimationDuration,
|
height: viewportHeight,
|
||||||
child: AssetDetails(minHeight: viewportHeight / 3 * 2),
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: const Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [AssetStackRow(), ViewerBottomBar()],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user