import 'package:dismissible_page/dismissible_page.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/types/attachment.dart'; import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/attachment/attachment_zoom.dart'; import 'package:surface/widgets/attachment/attachment_item.dart'; import 'package:surface/widgets/dialog.dart'; import 'package:uuid/uuid.dart'; class AlbumScreen extends StatefulWidget { const AlbumScreen({super.key}); @override State createState() => _AlbumScreenState(); } class _AlbumScreenState extends State { final ScrollController _scrollController = ScrollController(); bool _isBusy = false; int? _totalCount; final List _attachments = List.empty(growable: true); final List _heroTags = List.empty(growable: true); Future _fetchAttachments() async { setState(() => _isBusy = true); const uuid = Uuid(); try { final sn = context.read(); final resp = await sn.client.get('/cgi/uc/attachments', queryParameters: { 'take': 10, 'offset': _attachments.length, }); final attachments = List.from( resp.data['data']?.map((e) => SnAttachment.fromJson(e)) ?? [], ).where((e) => e.mimetype.startsWith('image')).toList(); _attachments.addAll(attachments); _heroTags.addAll(_attachments.map((_) => uuid.v4())); _totalCount = resp.data['count'] as int?; } catch (err) { if (!mounted) return; context.showErrorDialog(err); } finally { setState(() => _isBusy = false); } } @override void initState() { super.initState(); _fetchAttachments(); _scrollController.addListener(() { if (_scrollController.position.atEdge) { bool isTop = _scrollController.position.pixels == 0; if (!isTop && !_isBusy) { if (_totalCount == null || _attachments.length < _totalCount!) { _fetchAttachments(); } } } }); } @override void dispose() { super.dispose(); _scrollController.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( controller: _scrollController, slivers: [ SliverAppBar( leading: AutoAppBarLeading(), title: Text('screenAlbum').tr(), ), SliverMasonryGrid.extent( childCount: _attachments.length, maxCrossAxisExtent: 320, mainAxisSpacing: 4, crossAxisSpacing: 4, itemBuilder: (context, idx) { final attachment = _attachments[idx]; return GestureDetector( child: ClipRRect( child: AspectRatio( aspectRatio: attachment.metadata['ratio']?.toDouble() ?? 1, child: AttachmentItem( data: attachment, heroTag: _heroTags[idx], ), ), ), onTap: () { context.pushTransparentRoute( AttachmentZoomView( data: [attachment], heroTags: [_heroTags[idx]], ), backgroundColor: Colors.black.withOpacity(0.7), rootNavigator: true, ); }, ); }, ), if (_isBusy) SliverToBoxAdapter( child: const CircularProgressIndicator().padding(all: 24).center(), ), ], ), ); } }