mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:39:03 -04:00 
			
		
		
		
	feat(mobile): Responsive layout improvements with a navigation rail and album grid (#1583)
This commit is contained in:
		
							parent
							
								
									18647203cc
								
							
						
					
					
						commit
						dc9da7480c
					
				| @ -1,18 +1,19 @@ | |||||||
| import 'package:auto_route/auto_route.dart'; |  | ||||||
| import 'package:cached_network_image/cached_network_image.dart'; | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:hive/hive.dart'; | import 'package:hive/hive.dart'; | ||||||
| import 'package:immich_mobile/constants/hive_box.dart'; | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
| import 'package:immich_mobile/routing/router.dart'; |  | ||||||
| import 'package:immich_mobile/shared/models/album.dart'; | import 'package:immich_mobile/shared/models/album.dart'; | ||||||
| import 'package:immich_mobile/utils/image_url_builder.dart'; | import 'package:immich_mobile/utils/image_url_builder.dart'; | ||||||
| import 'package:openapi/api.dart'; | import 'package:openapi/api.dart'; | ||||||
| 
 | 
 | ||||||
| class AlbumThumbnailCard extends StatelessWidget { | class AlbumThumbnailCard extends StatelessWidget { | ||||||
|  |   final Function()? onTap; | ||||||
|  | 
 | ||||||
|   const AlbumThumbnailCard({ |   const AlbumThumbnailCard({ | ||||||
|     Key? key, |     Key? key, | ||||||
|     required this.album, |     required this.album, | ||||||
|  |     this.onTap, | ||||||
|   }) : super(key: key); |   }) : super(key: key); | ||||||
| 
 | 
 | ||||||
|   final Album album; |   final Album album; | ||||||
| @ -20,89 +21,94 @@ class AlbumThumbnailCard extends StatelessWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     var box = Hive.box(userInfoBox); |     var box = Hive.box(userInfoBox); | ||||||
|     var cardSize = MediaQuery.of(context).size.width / 2 - 18; |  | ||||||
|     var isDarkMode = Theme.of(context).brightness == Brightness.dark; |     var isDarkMode = Theme.of(context).brightness == Brightness.dark; | ||||||
|  |     return LayoutBuilder( | ||||||
|  |       builder: (context, constraints) { | ||||||
|  |       var cardSize = constraints.maxWidth; | ||||||
| 
 | 
 | ||||||
|     buildEmptyThumbnail() { |       buildEmptyThumbnail() { | ||||||
|       return Container( |         return Container( | ||||||
|         decoration: BoxDecoration( |  | ||||||
|           color: isDarkMode ? Colors.grey[800] : Colors.grey[200], |  | ||||||
|         ), |  | ||||||
|         child: SizedBox( |  | ||||||
|           height: cardSize, |           height: cardSize, | ||||||
|           width: cardSize, |           width: cardSize, | ||||||
|           child: const Center( |           decoration: BoxDecoration( | ||||||
|             child: Icon(Icons.no_photography), |             color: isDarkMode ? Colors.grey[800] : Colors.grey[200], | ||||||
|           ), |           ), | ||||||
|         ), |           child: Center( | ||||||
|       ); |             child: Icon( | ||||||
|     } |               Icons.no_photography, | ||||||
| 
 |               size: cardSize * .15, | ||||||
|     buildAlbumThumbnail() { |  | ||||||
|       return CachedNetworkImage( |  | ||||||
|         width: cardSize, |  | ||||||
|         height: cardSize, |  | ||||||
|         fit: BoxFit.cover, |  | ||||||
|         fadeInDuration: const Duration(milliseconds: 200), |  | ||||||
|         imageUrl: getAlbumThumbnailUrl( |  | ||||||
|           album, |  | ||||||
|           type: ThumbnailFormat.JPEG, |  | ||||||
|         ), |  | ||||||
|         httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"}, |  | ||||||
|         cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG), |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return GestureDetector( |  | ||||||
|       onTap: () { |  | ||||||
|         AutoRouter.of(context).push(AlbumViewerRoute(albumId: album.id)); |  | ||||||
|       }, |  | ||||||
|       child: Padding( |  | ||||||
|         padding: const EdgeInsets.only(bottom: 32.0), |  | ||||||
|         child: Column( |  | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|           children: [ |  | ||||||
|             ClipRRect( |  | ||||||
|               borderRadius: BorderRadius.circular(8), |  | ||||||
|               child: album.albumThumbnailAssetId == null |  | ||||||
|                   ? buildEmptyThumbnail() |  | ||||||
|                   : buildAlbumThumbnail(), |  | ||||||
|             ), |             ), | ||||||
|             Padding( |           ), | ||||||
|               padding: const EdgeInsets.only(top: 8.0), |         ); | ||||||
|               child: SizedBox( |       } | ||||||
|                 width: cardSize, | 
 | ||||||
|                 child: Text( |       buildAlbumThumbnail() { | ||||||
|                   album.name, |         return CachedNetworkImage( | ||||||
|                   style: const TextStyle( |           width: cardSize, | ||||||
|                     fontWeight: FontWeight.bold, |           height: cardSize, | ||||||
|  |           fit: BoxFit.cover, | ||||||
|  |           fadeInDuration: const Duration(milliseconds: 200), | ||||||
|  |           imageUrl: getAlbumThumbnailUrl( | ||||||
|  |             album, | ||||||
|  |             type: ThumbnailFormat.JPEG, | ||||||
|  |           ), | ||||||
|  |           httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"}, | ||||||
|  |           cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG), | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       return GestureDetector( | ||||||
|  |         onTap: onTap, | ||||||
|  |         child: Padding( | ||||||
|  |           padding: const EdgeInsets.only(bottom: 32.0), | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |             children: [ | ||||||
|  |               Expanded( | ||||||
|  |                 child: ClipRRect( | ||||||
|  |                   borderRadius: BorderRadius.circular(8), | ||||||
|  |                   child: album.albumThumbnailAssetId == null | ||||||
|  |                     ? buildEmptyThumbnail() | ||||||
|  |                     : buildAlbumThumbnail(), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               Padding( | ||||||
|  |                 padding: const EdgeInsets.only(top: 8.0), | ||||||
|  |                 child: SizedBox( | ||||||
|  |                   width: cardSize, | ||||||
|  |                   child: Text( | ||||||
|  |                     album.name, | ||||||
|  |                     style: const TextStyle( | ||||||
|  |                       fontWeight: FontWeight.bold, | ||||||
|  |                     ), | ||||||
|                   ), |                   ), | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |               Row( | ||||||
|             Row( |                 mainAxisSize: MainAxisSize.min, | ||||||
|               mainAxisSize: MainAxisSize.min, |                 children: [ | ||||||
|               children: [ |                   Text( | ||||||
|                 Text( |                     album.assetCount == 1 | ||||||
|                   album.assetCount == 1 |                         ? 'album_thumbnail_card_item' | ||||||
|                       ? 'album_thumbnail_card_item' |                         : 'album_thumbnail_card_items', | ||||||
|                       : 'album_thumbnail_card_items', |                     style: const TextStyle( | ||||||
|                   style: const TextStyle( |  | ||||||
|                     fontSize: 12, |  | ||||||
|                   ), |  | ||||||
|                 ).tr(args: ['${album.assetCount}']), |  | ||||||
|                 if (album.shared) |  | ||||||
|                   const Text( |  | ||||||
|                     'album_thumbnail_card_shared', |  | ||||||
|                     style: TextStyle( |  | ||||||
|                       fontSize: 12, |                       fontSize: 12, | ||||||
|                     ), |                     ), | ||||||
|                   ).tr() |                   ).tr(args: ['${album.assetCount}']), | ||||||
|               ], |                   if (album.shared) | ||||||
|             ) |                     const Text( | ||||||
|           ], |                       'album_thumbnail_card_shared', | ||||||
|  |                       style: TextStyle( | ||||||
|  |                         fontSize: 12, | ||||||
|  |                       ), | ||||||
|  |                     ).tr() | ||||||
|  |                 ], | ||||||
|  |               ) | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|         ), |         ), | ||||||
|       ), |       ); | ||||||
|  |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -112,37 +112,43 @@ class LibraryPage extends HookConsumerWidget { | |||||||
|         onTap: () { |         onTap: () { | ||||||
|           AutoRouter.of(context).push(CreateAlbumRoute(isSharedAlbum: false)); |           AutoRouter.of(context).push(CreateAlbumRoute(isSharedAlbum: false)); | ||||||
|         }, |         }, | ||||||
|         child: Column( |         child: Padding( | ||||||
|           mainAxisAlignment: MainAxisAlignment.start, |           padding: const EdgeInsets.only(bottom: 32), | ||||||
|           crossAxisAlignment: CrossAxisAlignment.start, |           child: Column( | ||||||
|           children: [ |             mainAxisAlignment: MainAxisAlignment.start, | ||||||
|             Container( |             crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|               width: MediaQuery.of(context).size.width / 2 - 18, |             children: [ | ||||||
|               height: MediaQuery.of(context).size.width / 2 - 18, |               Expanded( | ||||||
|               decoration: BoxDecoration( |                 child: Container( | ||||||
|                 border: Border.all( |                   decoration: BoxDecoration( | ||||||
|                   color: Colors.grey, |                     border: Border.all( | ||||||
|                 ), |                       color: Colors.grey, | ||||||
|                 borderRadius: BorderRadius.circular(8), |                     ), | ||||||
|               ), |                     borderRadius: BorderRadius.circular(8), | ||||||
|               child: Center( |                   ), | ||||||
|                 child: Icon( |                   child: Center( | ||||||
|                   Icons.add_rounded, |                     child: Icon( | ||||||
|                   size: 28, |                       Icons.add_rounded, | ||||||
|                   color: Theme.of(context).primaryColor, |                       size: 28, | ||||||
|  |                       color: Theme.of(context).primaryColor, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|                 ), |                 ), | ||||||
|               ), |               ), | ||||||
|             ), |               Padding( | ||||||
|             Padding( |                 padding: const EdgeInsets.only( | ||||||
|               padding: const EdgeInsets.only(top: 8.0), |                   top: 8.0, | ||||||
|               child: const Text( |                   bottom: 16, | ||||||
|                 'library_page_new_album', |  | ||||||
|                 style: TextStyle( |  | ||||||
|                   fontWeight: FontWeight.bold, |  | ||||||
|                 ), |                 ), | ||||||
|               ).tr(), |                 child: const Text( | ||||||
|             ) |                   'library_page_new_album', | ||||||
|           ], |                   style: TextStyle( | ||||||
|  |                     fontWeight: FontWeight.bold, | ||||||
|  |                   ), | ||||||
|  |                 ).tr(), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|         ), |         ), | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| @ -185,6 +191,8 @@ class LibraryPage extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     final sorted = sortedAlbums(); | ||||||
|  | 
 | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: buildAppBar(), |       appBar: buildAppBar(), | ||||||
|       body: CustomScrollView( |       body: CustomScrollView( | ||||||
| @ -234,20 +242,33 @@ class LibraryPage extends HookConsumerWidget { | |||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           SliverPadding( |           SliverPadding( | ||||||
|             padding: const EdgeInsets.only(left: 12.0, right: 12, bottom: 50), |             padding: const EdgeInsets.all(12.0), | ||||||
|             sliver: SliverToBoxAdapter( |             sliver: SliverGrid( | ||||||
|               child: Wrap( |               gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( | ||||||
|                 spacing: 12, |                 maxCrossAxisExtent: 250, | ||||||
|                 children: [ |                 mainAxisSpacing: 12, | ||||||
|                   buildCreateAlbumButton(), |                 crossAxisSpacing: 12, | ||||||
|                   for (var album in sortedAlbums()) |                 childAspectRatio: .7, | ||||||
|                     AlbumThumbnailCard( |               ), | ||||||
|                       album: album, |               delegate: SliverChildBuilderDelegate( | ||||||
|  |                 childCount: sorted.length + 1, | ||||||
|  |                 (context, index) { | ||||||
|  |                   if (index  == 0) { | ||||||
|  |                     return buildCreateAlbumButton(); | ||||||
|  |                   } | ||||||
|  | 
 | ||||||
|  |                   return AlbumThumbnailCard( | ||||||
|  |                     album: sorted[index - 1], | ||||||
|  |                     onTap: () => AutoRouter.of(context).push( | ||||||
|  |                       AlbumViewerRoute( | ||||||
|  |                         albumId: sorted[index - 1].id, | ||||||
|  |                       ), | ||||||
|                     ), |                     ), | ||||||
|                 ], |                   ); | ||||||
|  |                 }, | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|           ) |           ), | ||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -66,11 +66,6 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> { | |||||||
|         assets.firstWhereOrNull((e) => !_selectedAssets.contains(e.id)) == null; |         assets.firstWhereOrNull((e) => !_selectedAssets.contains(e.id)) == null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   double _getItemSize(BuildContext context) { |  | ||||||
|     return MediaQuery.of(context).size.width / widget.assetsPerRow - |  | ||||||
|         widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   Widget _buildThumbnailOrPlaceholder( |   Widget _buildThumbnailOrPlaceholder( | ||||||
|     Asset asset, |     Asset asset, | ||||||
|     bool placeholder, |     bool placeholder, | ||||||
| @ -97,24 +92,29 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> { | |||||||
|     RenderAssetGridRow row, |     RenderAssetGridRow row, | ||||||
|     bool scrolling, |     bool scrolling, | ||||||
|   ) { |   ) { | ||||||
|     double size = _getItemSize(context); |  | ||||||
| 
 | 
 | ||||||
|     return Row( |     return LayoutBuilder( | ||||||
|       key: Key("asset-row-${row.assets.first.id}"), |       builder: (context, constraints) { | ||||||
|       children: row.assets.map((Asset asset) { |         final size = constraints.maxWidth / widget.assetsPerRow - | ||||||
|         bool last = asset.id == row.assets.last.id; |           widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow; | ||||||
|  |         return Row( | ||||||
|  |           key: Key("asset-row-${row.assets.first.id}"), | ||||||
|  |           children: row.assets.map((Asset asset) { | ||||||
|  |             bool last = asset.id == row.assets.last.id; | ||||||
| 
 | 
 | ||||||
|         return Container( |             return Container( | ||||||
|           key: Key("asset-${asset.id}"), |               key: Key("asset-${asset.id}"), | ||||||
|           width: size, |               width: size, | ||||||
|           height: size, |               height: size, | ||||||
|           margin: EdgeInsets.only( |               margin: EdgeInsets.only( | ||||||
|             top: widget.margin, |                 top: widget.margin, | ||||||
|             right: last ? 0.0 : widget.margin, |                 right: last ? 0.0 : widget.margin, | ||||||
|           ), |               ), | ||||||
|           child: _buildThumbnailOrPlaceholder(asset, scrolling), |               child: _buildThumbnailOrPlaceholder(asset, scrolling), | ||||||
|  |             ); | ||||||
|  |           }).toList(), | ||||||
|         ); |         ); | ||||||
|       }).toList(), |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,6 +11,96 @@ class TabControllerPage extends ConsumerWidget { | |||||||
| 
 | 
 | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  | 
 | ||||||
|  |     navigationRail(TabsRouter tabsRouter) { | ||||||
|  |       return NavigationRail( | ||||||
|  |         labelType: NavigationRailLabelType.all, | ||||||
|  |         selectedIndex: tabsRouter.activeIndex, | ||||||
|  |         onDestinationSelected: (index) { | ||||||
|  |           HapticFeedback.selectionClick(); | ||||||
|  |           tabsRouter.setActiveIndex(index); | ||||||
|  |         }, | ||||||
|  |         selectedIconTheme: IconThemeData( | ||||||
|  |           color: Theme.of(context).primaryColor, | ||||||
|  |         ), | ||||||
|  |         selectedLabelTextStyle: TextStyle( | ||||||
|  |           color: Theme.of(context).primaryColor, | ||||||
|  |         ), | ||||||
|  |         useIndicator: false, | ||||||
|  |         destinations: [ | ||||||
|  |           NavigationRailDestination( | ||||||
|  |             padding: EdgeInsets.only( | ||||||
|  |               top: MediaQuery.of(context).padding.top + 4, | ||||||
|  |               left: 4, | ||||||
|  |               right: 4, | ||||||
|  |               bottom: 4, | ||||||
|  |             ), | ||||||
|  |             icon: const Icon(Icons.photo_outlined),  | ||||||
|  |             selectedIcon: const Icon(Icons.photo), | ||||||
|  |             label: const Text('tab_controller_nav_photos').tr(), | ||||||
|  |           ), | ||||||
|  |           NavigationRailDestination( | ||||||
|  |             padding: const EdgeInsets.all(4), | ||||||
|  |             icon: const Icon(Icons.search_rounded),  | ||||||
|  |             selectedIcon: const Icon(Icons.search),  | ||||||
|  |             label: const Text('tab_controller_nav_search').tr(), | ||||||
|  |           ), | ||||||
|  |           NavigationRailDestination( | ||||||
|  |             padding: const EdgeInsets.all(4), | ||||||
|  |             icon: const Icon(Icons.share_rounded),  | ||||||
|  |             selectedIcon: const Icon(Icons.share),  | ||||||
|  |             label: const Text('tab_controller_nav_sharing').tr(), | ||||||
|  |           ), | ||||||
|  |           NavigationRailDestination( | ||||||
|  |             padding: const EdgeInsets.all(4), | ||||||
|  |             icon: const Icon(Icons.photo_album_outlined),  | ||||||
|  |             selectedIcon: const Icon(Icons.photo_album),  | ||||||
|  |             label: const Text('tab_controller_nav_library').tr(), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bottomNavigationBar(TabsRouter tabsRouter) { | ||||||
|  |       return BottomNavigationBar( | ||||||
|  |         selectedLabelStyle: const TextStyle( | ||||||
|  |           fontSize: 13, | ||||||
|  |           fontWeight: FontWeight.w600, | ||||||
|  |         ), | ||||||
|  |         unselectedLabelStyle: const TextStyle( | ||||||
|  |           fontSize: 13, | ||||||
|  |           fontWeight: FontWeight.w600, | ||||||
|  |         ), | ||||||
|  |         currentIndex: tabsRouter.activeIndex, | ||||||
|  |         onTap: (index) { | ||||||
|  |           HapticFeedback.selectionClick(); | ||||||
|  |           tabsRouter.setActiveIndex(index); | ||||||
|  |         }, | ||||||
|  |         items: [ | ||||||
|  |           BottomNavigationBarItem( | ||||||
|  |             label: 'tab_controller_nav_photos'.tr(), | ||||||
|  |             icon: const Icon(Icons.photo_outlined), | ||||||
|  |             activeIcon: const Icon(Icons.photo), | ||||||
|  |           ), | ||||||
|  |           BottomNavigationBarItem( | ||||||
|  |             label: 'tab_controller_nav_search'.tr(), | ||||||
|  |             icon: const Icon(Icons.search_rounded), | ||||||
|  |             activeIcon: const Icon(Icons.search), | ||||||
|  |           ), | ||||||
|  |           BottomNavigationBarItem( | ||||||
|  |             label: 'tab_controller_nav_sharing'.tr(), | ||||||
|  |             icon: const Icon(Icons.group_outlined), | ||||||
|  |             activeIcon: const Icon(Icons.group), | ||||||
|  |           ), | ||||||
|  |           BottomNavigationBarItem( | ||||||
|  |             label: 'tab_controller_nav_library'.tr(), | ||||||
|  |             icon: const Icon(Icons.photo_album_outlined), | ||||||
|  |             activeIcon: const Icon(Icons.photo_album_rounded), | ||||||
|  |           ) | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     final multiselectEnabled = ref.watch(multiselectProvider); |     final multiselectEnabled = ref.watch(multiselectProvider); | ||||||
|     return AutoTabsRouter( |     return AutoTabsRouter( | ||||||
|       routes: [ |       routes: [ | ||||||
| @ -32,51 +122,39 @@ class TabControllerPage extends ConsumerWidget { | |||||||
|             } |             } | ||||||
|             return atHomeTab; |             return atHomeTab; | ||||||
|           }, |           }, | ||||||
|           child: Scaffold( |           child: LayoutBuilder( | ||||||
|             body: FadeTransition( |             builder: (context, constraints) { | ||||||
|               opacity: animation, |               const medium = 600; | ||||||
|               child: child, |               final Widget? bottom; | ||||||
|             ), |               final Widget body; | ||||||
|             bottomNavigationBar: multiselectEnabled |               if (constraints.maxWidth < medium) { | ||||||
|                 ? null |                 // Normal phone width | ||||||
|                 : BottomNavigationBar( |                 bottom = bottomNavigationBar(tabsRouter); | ||||||
|                     selectedLabelStyle: const TextStyle( |                 body = FadeTransition( | ||||||
|                       fontSize: 13, |                   opacity: animation, | ||||||
|                       fontWeight: FontWeight.w600, |                   child: child, | ||||||
|  |                 ); | ||||||
|  |               } else { | ||||||
|  |                 // Medium tablet width | ||||||
|  |                 bottom = null; | ||||||
|  |                 body = Row( | ||||||
|  |                   children: [ | ||||||
|  |                     navigationRail(tabsRouter), | ||||||
|  |                     Expanded( | ||||||
|  |                       child: FadeTransition( | ||||||
|  |                         opacity: animation, | ||||||
|  |                         child: child, | ||||||
|  |                       ), | ||||||
|                     ), |                     ), | ||||||
|                     unselectedLabelStyle: const TextStyle( |                   ], | ||||||
|                       fontSize: 13, |                 ); | ||||||
|                       fontWeight: FontWeight.w600, |               }              return Scaffold( | ||||||
|                     ), |                body: body, | ||||||
|                     currentIndex: tabsRouter.activeIndex, |                bottomNavigationBar: multiselectEnabled | ||||||
|                     onTap: (index) { |                   ? null | ||||||
|                       HapticFeedback.selectionClick(); |                   : bottom, | ||||||
|                       tabsRouter.setActiveIndex(index); |             ); | ||||||
|                     }, |           },), | ||||||
|                     items: [ |  | ||||||
|                       BottomNavigationBarItem( |  | ||||||
|                         label: 'tab_controller_nav_photos'.tr(), |  | ||||||
|                         icon: const Icon(Icons.photo_outlined), |  | ||||||
|                         activeIcon: const Icon(Icons.photo), |  | ||||||
|                       ), |  | ||||||
|                       BottomNavigationBarItem( |  | ||||||
|                         label: 'tab_controller_nav_search'.tr(), |  | ||||||
|                         icon: const Icon(Icons.search_rounded), |  | ||||||
|                         activeIcon: const Icon(Icons.search), |  | ||||||
|                       ), |  | ||||||
|                       BottomNavigationBarItem( |  | ||||||
|                         label: 'tab_controller_nav_sharing'.tr(), |  | ||||||
|                         icon: const Icon(Icons.group_outlined), |  | ||||||
|                         activeIcon: const Icon(Icons.group), |  | ||||||
|                       ), |  | ||||||
|                       BottomNavigationBarItem( |  | ||||||
|                         label: 'tab_controller_nav_library'.tr(), |  | ||||||
|                         icon: const Icon(Icons.photo_album_outlined), |  | ||||||
|                         activeIcon: const Icon(Icons.photo_album_rounded), |  | ||||||
|                       ) |  | ||||||
|                     ], |  | ||||||
|                   ), |  | ||||||
|           ), |  | ||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|     ); |     ); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user