mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	feat(mobile) Enhance bottom app bar on home page (#934)
* Added bottom sheet * Finished styling bottom app bar * Fixed border radius
This commit is contained in:
		
							parent
							
								
									c8538cc62f
								
							
						
					
					
						commit
						f0874ff3fd
					
				@ -28,34 +28,30 @@ class ControlBottomAppBar extends ConsumerWidget {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
    Widget renderActionButtons() {
 | 
					    Widget renderActionButtons() {
 | 
				
			||||||
      return Padding(
 | 
					      return Row(
 | 
				
			||||||
        padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
 | 
					        children: [
 | 
				
			||||||
        child: Row(
 | 
					          ControlBoxButton(
 | 
				
			||||||
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
					            iconData: Icons.ios_share_rounded,
 | 
				
			||||||
          children: [
 | 
					            label: "control_bottom_app_bar_share".tr(),
 | 
				
			||||||
            ControlBoxButton(
 | 
					            onPressed: () {
 | 
				
			||||||
              iconData: Icons.delete_forever_rounded,
 | 
					              onShare();
 | 
				
			||||||
              label: "control_bottom_app_bar_delete".tr(),
 | 
					            },
 | 
				
			||||||
              onPressed: () {
 | 
					          ),
 | 
				
			||||||
                showDialog(
 | 
					          ControlBoxButton(
 | 
				
			||||||
                  context: context,
 | 
					            iconData: Icons.delete_outline_rounded,
 | 
				
			||||||
                  builder: (BuildContext context) {
 | 
					            label: "control_bottom_app_bar_delete".tr(),
 | 
				
			||||||
                    return DeleteDialog(
 | 
					            onPressed: () {
 | 
				
			||||||
                      onDelete: onDelete,
 | 
					              showDialog(
 | 
				
			||||||
                    );
 | 
					                context: context,
 | 
				
			||||||
                  },
 | 
					                builder: (BuildContext context) {
 | 
				
			||||||
                );
 | 
					                  return DeleteDialog(
 | 
				
			||||||
              },
 | 
					                    onDelete: onDelete,
 | 
				
			||||||
            ),
 | 
					                  );
 | 
				
			||||||
            ControlBoxButton(
 | 
					                },
 | 
				
			||||||
              iconData: Icons.share,
 | 
					              );
 | 
				
			||||||
              label: "control_bottom_app_bar_share".tr(),
 | 
					            },
 | 
				
			||||||
              onPressed: () {
 | 
					          ),
 | 
				
			||||||
                onShare();
 | 
					        ],
 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -63,40 +59,44 @@ class ControlBottomAppBar extends ConsumerWidget {
 | 
				
			|||||||
      Widget renderAlbum(AlbumResponseDto album) {
 | 
					      Widget renderAlbum(AlbumResponseDto album) {
 | 
				
			||||||
        final box = Hive.box(userInfoBox);
 | 
					        final box = Hive.box(userInfoBox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return GestureDetector(
 | 
					        return Padding(
 | 
				
			||||||
          onTap: () => onAddToAlbum(album),
 | 
					          padding: const EdgeInsets.only(left: 8.0),
 | 
				
			||||||
          child: Container(
 | 
					          child: GestureDetector(
 | 
				
			||||||
            width: 112,
 | 
					            onTap: () => onAddToAlbum(album),
 | 
				
			||||||
            padding: const EdgeInsets.all(6),
 | 
					            child: Container(
 | 
				
			||||||
            child: Column(
 | 
					              width: 112,
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
					              padding: const EdgeInsets.all(6),
 | 
				
			||||||
              children: [
 | 
					              child: Column(
 | 
				
			||||||
                ClipRRect(
 | 
					                crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
                  borderRadius: BorderRadius.circular(8),
 | 
					                children: [
 | 
				
			||||||
                  child: CachedNetworkImage(
 | 
					                  ClipRRect(
 | 
				
			||||||
                    width: 100,
 | 
					                    borderRadius: BorderRadius.circular(8),
 | 
				
			||||||
                    height: 100,
 | 
					                    child: CachedNetworkImage(
 | 
				
			||||||
                    fit: BoxFit.cover,
 | 
					                      width: 100,
 | 
				
			||||||
                    imageUrl:
 | 
					                      height: 100,
 | 
				
			||||||
                        getAlbumThumbnailUrl(album, type: ThumbnailFormat.JPEG),
 | 
					                      fit: BoxFit.cover,
 | 
				
			||||||
                    httpHeaders: {
 | 
					                      imageUrl: getAlbumThumbnailUrl(
 | 
				
			||||||
                      "Authorization": "Bearer ${box.get(accessTokenKey)}"
 | 
					                        album,
 | 
				
			||||||
                    },
 | 
					                        type: ThumbnailFormat.JPEG,
 | 
				
			||||||
                    cacheKey: "${album.albumThumbnailAssetId}",
 | 
					                      ),
 | 
				
			||||||
 | 
					                      httpHeaders: {
 | 
				
			||||||
 | 
					                        "Authorization": "Bearer ${box.get(accessTokenKey)}"
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                      cacheKey: "${album.albumThumbnailAssetId}",
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                  Padding(
 | 
				
			||||||
                Padding(
 | 
					                    padding: const EdgeInsets.only(top: 12),
 | 
				
			||||||
                  padding: const EdgeInsets.only(top: 12),
 | 
					                    child: Text(
 | 
				
			||||||
                  child: Text(
 | 
					                      album.albumName,
 | 
				
			||||||
                    album.albumName,
 | 
					                      style: const TextStyle(
 | 
				
			||||||
                    style: TextStyle(fontWeight: FontWeight.bold),
 | 
					                        fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                        fontSize: 12.0,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ],
 | 
				
			||||||
                Text(album.shared
 | 
					              ),
 | 
				
			||||||
                        ? "control_bottom_app_bar_album_info_shared"
 | 
					 | 
				
			||||||
                        : "control_bottom_app_bar_album_info")
 | 
					 | 
				
			||||||
                    .tr(args: [album.assetCount.toString()]),
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -112,52 +112,109 @@ class ControlBottomAppBar extends ConsumerWidget {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Positioned(
 | 
					    return DraggableScrollableSheet(
 | 
				
			||||||
      bottom: 0,
 | 
					      initialChildSize: 0.30,
 | 
				
			||||||
      left: 0,
 | 
					      minChildSize: 0.15,
 | 
				
			||||||
      child: Container(
 | 
					      maxChildSize: 0.57,
 | 
				
			||||||
        width: MediaQuery.of(context).size.width,
 | 
					      snap: true,
 | 
				
			||||||
        decoration: BoxDecoration(
 | 
					      builder: (
 | 
				
			||||||
          borderRadius: const BorderRadius.only(
 | 
					        BuildContext context,
 | 
				
			||||||
            topLeft: Radius.circular(10),
 | 
					        ScrollController scrollController,
 | 
				
			||||||
            topRight: Radius.circular(10),
 | 
					      ) {
 | 
				
			||||||
          ),
 | 
					        return SingleChildScrollView(
 | 
				
			||||||
          color: Theme.of(context).scaffoldBackgroundColor,
 | 
					          controller: scrollController,
 | 
				
			||||||
        ),
 | 
					          child: Card(
 | 
				
			||||||
        child: Column(
 | 
					            elevation: 12.0,
 | 
				
			||||||
          crossAxisAlignment: CrossAxisAlignment.start,
 | 
					            shape: const RoundedRectangleBorder(
 | 
				
			||||||
          children: [
 | 
					              borderRadius: BorderRadius.only(
 | 
				
			||||||
            renderActionButtons(),
 | 
					                topLeft: Radius.circular(12),
 | 
				
			||||||
            const Divider(
 | 
					                topRight: Radius.circular(12),
 | 
				
			||||||
              thickness: 2,
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Padding(
 | 
					            margin: const EdgeInsets.all(0),
 | 
				
			||||||
                padding: const EdgeInsets.all(12),
 | 
					            child: Container(
 | 
				
			||||||
                child: Row(
 | 
					              decoration: const BoxDecoration(
 | 
				
			||||||
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
					                borderRadius: BorderRadius.only(
 | 
				
			||||||
                  children: [
 | 
					                  topLeft: Radius.circular(12),
 | 
				
			||||||
                    const Text(
 | 
					                  topRight: Radius.circular(12),
 | 
				
			||||||
                      "control_bottom_app_bar_add_to_album",
 | 
					                ),
 | 
				
			||||||
                      style: TextStyle(
 | 
					              ),
 | 
				
			||||||
                        fontSize: 16,
 | 
					              child: Column(
 | 
				
			||||||
                        fontWeight: FontWeight.bold,
 | 
					                children: <Widget>[
 | 
				
			||||||
                      ),
 | 
					                  const SizedBox(height: 12),
 | 
				
			||||||
                    ).tr(),
 | 
					                  const CustomDraggingHandle(),
 | 
				
			||||||
                    TextButton(
 | 
					                  const SizedBox(height: 12),
 | 
				
			||||||
                      onPressed: onCreateNewAlbum,
 | 
					                  renderActionButtons(),
 | 
				
			||||||
                      child: Text(
 | 
					                  const Divider(
 | 
				
			||||||
                        "control_bottom_app_bar_create_new_album",
 | 
					                    indent: 16,
 | 
				
			||||||
                        style: TextStyle(
 | 
					                    endIndent: 16,
 | 
				
			||||||
                          color: Theme.of(context).primaryColor,
 | 
					                    thickness: 1,
 | 
				
			||||||
                          fontWeight: FontWeight.bold,
 | 
					                  ),
 | 
				
			||||||
                        ),
 | 
					                  AddToAlbumTitleRow(
 | 
				
			||||||
                      ).tr(),
 | 
					                    onCreateNewAlbum: () => onCreateNewAlbum(),
 | 
				
			||||||
                    ),
 | 
					                  ),
 | 
				
			||||||
                  ],
 | 
					                  renderAlbums(),
 | 
				
			||||||
                )),
 | 
					                  const SizedBox(height: 12),
 | 
				
			||||||
            renderAlbums(),
 | 
					                ],
 | 
				
			||||||
          ],
 | 
					              ),
 | 
				
			||||||
        ),
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddToAlbumTitleRow extends StatelessWidget {
 | 
				
			||||||
 | 
					  const AddToAlbumTitleRow({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.onCreateNewAlbum,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final VoidCallback onCreateNewAlbum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Padding(
 | 
				
			||||||
 | 
					      padding: const EdgeInsets.symmetric(horizontal: 16),
 | 
				
			||||||
 | 
					      child: Row(
 | 
				
			||||||
 | 
					        mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          const Text(
 | 
				
			||||||
 | 
					            "control_bottom_app_bar_add_to_album",
 | 
				
			||||||
 | 
					            style: TextStyle(
 | 
				
			||||||
 | 
					              fontSize: 14,
 | 
				
			||||||
 | 
					              fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ).tr(),
 | 
				
			||||||
 | 
					          TextButton(
 | 
				
			||||||
 | 
					            onPressed: onCreateNewAlbum,
 | 
				
			||||||
 | 
					            child: Text(
 | 
				
			||||||
 | 
					              "control_bottom_app_bar_create_new_album",
 | 
				
			||||||
 | 
					              style: TextStyle(
 | 
				
			||||||
 | 
					                color: Theme.of(context).primaryColor,
 | 
				
			||||||
 | 
					                fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					                fontSize: 14,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ).tr(),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CustomDraggingHandle extends StatelessWidget {
 | 
				
			||||||
 | 
					  const CustomDraggingHandle({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Container(
 | 
				
			||||||
 | 
					      height: 5,
 | 
				
			||||||
 | 
					      width: 30,
 | 
				
			||||||
 | 
					      decoration: BoxDecoration(
 | 
				
			||||||
 | 
					        color: Colors.grey[500],
 | 
				
			||||||
 | 
					        borderRadius: BorderRadius.circular(16),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -177,19 +234,20 @@ class ControlBoxButton extends StatelessWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return SizedBox(
 | 
					    return MaterialButton(
 | 
				
			||||||
      width: 60,
 | 
					      padding: const EdgeInsets.all(10),
 | 
				
			||||||
 | 
					      shape: const CircleBorder(),
 | 
				
			||||||
 | 
					      onPressed: () => onPressed(),
 | 
				
			||||||
      child: Column(
 | 
					      child: Column(
 | 
				
			||||||
        mainAxisAlignment: MainAxisAlignment.start,
 | 
					        mainAxisAlignment: MainAxisAlignment.start,
 | 
				
			||||||
        crossAxisAlignment: CrossAxisAlignment.center,
 | 
					        crossAxisAlignment: CrossAxisAlignment.center,
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          IconButton(
 | 
					          Icon(iconData, size: 24),
 | 
				
			||||||
            onPressed: () {
 | 
					          const SizedBox(height: 6),
 | 
				
			||||||
              onPressed();
 | 
					          Text(
 | 
				
			||||||
            },
 | 
					            label,
 | 
				
			||||||
            icon: Icon(iconData, size: 30),
 | 
					            style: const TextStyle(fontSize: 12.0),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          Text(label)
 | 
					 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
 | 
				
			|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
 | 
					import 'package:immich_mobile/modules/album/providers/album.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
 | 
					import 'package:immich_mobile/modules/album/services/album.service.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/providers/home_page_render_list_provider.dart';
 | 
					import 'package:immich_mobile/modules/home/providers/home_page_render_list_provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
 | 
					import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
 | 
				
			||||||
@ -79,10 +78,11 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      void onAddToAlbum(AlbumResponseDto album) async {
 | 
					      void onAddToAlbum(AlbumResponseDto album) async {
 | 
				
			||||||
        final result = await albumService.addAdditionalAssetToAlbum(
 | 
					        final result = await albumService.addAdditionalAssetToAlbum(
 | 
				
			||||||
            selection.value, album.id);
 | 
					          selection.value,
 | 
				
			||||||
 | 
					          album.id,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (result != null) {
 | 
					        if (result != null) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (result.alreadyInAlbum.isNotEmpty) {
 | 
					          if (result.alreadyInAlbum.isNotEmpty) {
 | 
				
			||||||
            ImmichToast.show(
 | 
					            ImmichToast.show(
 | 
				
			||||||
              context: context,
 | 
					              context: context,
 | 
				
			||||||
@ -130,14 +130,16 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
            CustomScrollView(
 | 
					            CustomScrollView(
 | 
				
			||||||
              slivers: [
 | 
					              slivers: [
 | 
				
			||||||
                if (!multiselectEnabled.state)
 | 
					                if (!multiselectEnabled.state)
 | 
				
			||||||
                ImmichSliverAppBar(
 | 
					                  ImmichSliverAppBar(
 | 
				
			||||||
                  onPopBack: reloadAllAsset,
 | 
					                    onPopBack: reloadAllAsset,
 | 
				
			||||||
                ),
 | 
					                  ),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Padding(
 | 
					            Padding(
 | 
				
			||||||
              padding: EdgeInsets.only(
 | 
					              padding: EdgeInsets.only(
 | 
				
			||||||
                  top: selectionEnabledHook.value ? 0 : 60, bottom: 0.0),
 | 
					                top: selectionEnabledHook.value ? 0 : 60,
 | 
				
			||||||
 | 
					                bottom: 0.0,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
              child: ImmichAssetGrid(
 | 
					              child: ImmichAssetGrid(
 | 
				
			||||||
                renderList: renderList,
 | 
					                renderList: renderList,
 | 
				
			||||||
                assetsPerRow:
 | 
					                assetsPerRow:
 | 
				
			||||||
@ -148,7 +150,7 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
                selectionActive: selectionEnabledHook.value,
 | 
					                selectionActive: selectionEnabledHook.value,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            if (selectionEnabledHook.value) ...[
 | 
					            if (selectionEnabledHook.value)
 | 
				
			||||||
              ControlBottomAppBar(
 | 
					              ControlBottomAppBar(
 | 
				
			||||||
                onShare: onShareAssets,
 | 
					                onShare: onShareAssets,
 | 
				
			||||||
                onDelete: onDelete,
 | 
					                onDelete: onDelete,
 | 
				
			||||||
@ -156,7 +158,6 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
                albums: albums,
 | 
					                albums: albums,
 | 
				
			||||||
                onCreateNewAlbum: onCreateNewAlbum,
 | 
					                onCreateNewAlbum: onCreateNewAlbum,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user