import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/bloc/list_album.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album_util.dart' as album_util; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/pref.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/widget/album_grid_item.dart'; import 'package:nc_photos/widget/builder/album_grid_item_builder.dart'; import 'package:nc_photos/widget/new_album_dialog.dart'; import 'package:nc_photos/widget/page_visibility_mixin.dart'; import 'package:np_codegen/np_codegen.dart'; part 'album_picker.g.dart'; class AlbumPickerArguments { AlbumPickerArguments(this.account); final Account account; } class AlbumPicker extends StatefulWidget { static const routeName = "/album-picker"; static Route buildRoute(AlbumPickerArguments args) => MaterialPageRoute( builder: (context) => AlbumPicker.fromArgs(args), ); const AlbumPicker({ Key? key, required this.account, }) : super(key: key); AlbumPicker.fromArgs(AlbumPickerArguments args, {Key? key}) : this( key: key, account: args.account, ); @override createState() => _AlbumPickerState(); final Account account; } @npLog class _AlbumPickerState extends State with RouteAware, PageVisibilityMixin { @override initState() { super.initState(); _initBloc(); } @override build(BuildContext context) { return Scaffold( body: BlocConsumer( bloc: _bloc, listener: (context, state) => _onStateChange(context, state), builder: (context, state) => _buildContent(context, state), ), ); } void _initBloc() { if (_bloc.state is ListAlbumBlocInit) { _log.info("[_initBloc] Initialize bloc"); _reqQuery(); } else { // process the current state WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { setState(() { _onStateChange(context, _bloc.state); }); } }); } } Widget _buildContent(BuildContext context, ListAlbumBlocState state) { return CustomScrollView( slivers: [ SliverAppBar( title: Text(L10n.global().addToAlbumTooltip), floating: true, ), if (state is ListAlbumBlocLoading) const SliverToBoxAdapter( child: LinearProgressIndicator(), ), SliverPadding( padding: const EdgeInsets.all(8), sliver: SliverStaggeredGrid.extentBuilder( maxCrossAxisExtent: 256, mainAxisSpacing: 8, staggeredTileBuilder: (_) => const StaggeredTile.count(1, 1), itemBuilder: (context, index) { if (index == 0) { return _buildNewAlbumItem(context); } else { return _buildItem(context, index - 1); } }, itemCount: _sortedAlbums.length + 1, ), ), ], ); } Widget _buildNewAlbumItem(BuildContext context) { return _NewAlbumView( onTap: () => _onNewAlbumPressed(context), ); } Widget _buildItem(BuildContext context, int index) { final item = _sortedAlbums[index]; return _AlbumView( account: widget.account, album: item, onTap: () => _onItemPressed(context, item), ); } void _onStateChange(BuildContext context, ListAlbumBlocState state) { if (state is ListAlbumBlocInit) { _sortedAlbums = []; } else if (state is ListAlbumBlocSuccess || state is ListAlbumBlocLoading) { _transformItems(state.items); } else if (state is ListAlbumBlocFailure) { _transformItems(state.items); if (isPageVisible()) { SnackBarManager().showSnackBar(SnackBar( content: Text(exception_util.toUserString(state.exception)), duration: k.snackBarDurationNormal, )); } } else if (state is ListAlbumBlocInconsistent) { _reqQuery(); } } Future _onNewAlbumPressed(BuildContext context) async { try { final album = await showDialog( context: context, builder: (_) => NewAlbumDialog( account: widget.account, isAllowDynamic: false, ), ); if (album == null) { // user canceled return; } Navigator.of(context).pop(album); } catch (e, stacktrace) { _log.shout("[_onNewAlbumPressed] Failed while showDialog", e, stacktrace); SnackBarManager().showSnackBar(SnackBar( content: Text(exception_util.toUserString(e)), duration: k.snackBarDurationNormal, )); } } void _onItemPressed(BuildContext context, Album album) { Navigator.of(context).pop(album); } void _transformItems(List items) { _sortedAlbums = album_util.sorted( items .map((e) => e.album) .where((a) => a.provider is AlbumStaticProvider) .toList(), _getSortFromPref()); } void _reqQuery() { _bloc.add(ListAlbumBlocQuery(widget.account)); } late final _bloc = ListAlbumBloc.of(widget.account); var _sortedAlbums = []; } class _NewAlbumView extends StatelessWidget { const _NewAlbumView({ this.onTap, }); @override Widget build(BuildContext context) { return Stack( children: [ AlbumGridItem( cover: ClipRRect( borderRadius: BorderRadius.circular(8), child: Container( color: Theme.of(context).listPlaceholderBackgroundColor, constraints: const BoxConstraints.expand(), child: Icon( Icons.add, color: Theme.of(context).listPlaceholderForegroundColor, size: 88, ), ), ), title: L10n.global().createAlbumTooltip, ), Positioned.fill( child: Material( type: MaterialType.transparency, child: InkWell( onTap: onTap, ), ), ), ], ); } final VoidCallback? onTap; } class _AlbumView extends StatelessWidget { const _AlbumView({ required this.account, required this.album, this.onTap, }); @override Widget build(BuildContext context) { return Stack( children: [ AlbumGridItemBuilder( account: account, album: album, isShared: album.shares?.isNotEmpty == true, ).build(context), Positioned.fill( child: Material( type: MaterialType.transparency, child: InkWell( onTap: onTap, ), ), ), ], ); } final Account account; final Album album; final VoidCallback? onTap; } album_util.AlbumSort _getSortFromPref() { try { return album_util.AlbumSort.values[Pref().getHomeAlbumsSort()!]; } catch (_) { // default return album_util.AlbumSort.dateDescending; } }