From ab73916795d509cdc3f8b7de1f4c3fac8b33ec59 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 10 Nov 2024 17:21:57 +0800 Subject: [PATCH] :sparkles: Detailed attachments --- lib/screens/post/post_editor.dart | 14 ++++-- lib/widgets/attachment/attachment_detail.dart | 37 +++++++++++++++ lib/widgets/attachment/attachment_item.dart | 42 ++++++++++++++--- lib/widgets/attachment/attachment_list.dart | 46 ++++++++++++++++--- pubspec.lock | 18 +++++++- pubspec.yaml | 3 ++ 6 files changed, 142 insertions(+), 18 deletions(-) create mode 100644 lib/widgets/attachment/attachment_detail.dart diff --git a/lib/screens/post/post_editor.dart b/lib/screens/post/post_editor.dart index 7eac538..c6384e8 100644 --- a/lib/screens/post/post_editor.dart +++ b/lib/screens/post/post_editor.dart @@ -338,11 +338,15 @@ class _PostEditorScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (_isBusy) - LinearProgressIndicator( - value: _progress, - minHeight: 2, - ), + if (_isBusy && _progress != null) + TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: Duration(milliseconds: 300), + builder: (context, value, _) => + LinearProgressIndicator(value: value, minHeight: 2), + ) + else if (_isBusy) + const LinearProgressIndicator(value: null, minHeight: 2), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/widgets/attachment/attachment_detail.dart b/lib/widgets/attachment/attachment_detail.dart new file mode 100644 index 0000000..0e268e4 --- /dev/null +++ b/lib/widgets/attachment/attachment_detail.dart @@ -0,0 +1,37 @@ +import 'package:dismissible_page/dismissible_page.dart'; +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:provider/provider.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/types/attachment.dart'; +import 'package:surface/widgets/universal_image.dart'; +import 'package:uuid/uuid.dart'; + +class AttachmentDetailPopup extends StatelessWidget { + final SnAttachment data; + final String? heroTag; + const AttachmentDetailPopup({super.key, required this.data, this.heroTag}); + + @override + Widget build(BuildContext context) { + final sn = context.read(); + final uuid = Uuid(); + + return DismissiblePage( + onDismissed: () { + Navigator.of(context).pop(); + }, + direction: DismissiblePageDismissDirection.down, + backgroundColor: Colors.transparent, + isFullScreen: true, + child: Hero( + tag: 'attachment-${data.rid}-${heroTag ?? uuid.v4()}', + child: PhotoView( + imageProvider: UniversalImage.provider( + sn.getAttachmentUrl(data.rid), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/attachment/attachment_item.dart b/lib/widgets/attachment/attachment_item.dart index 2696472..8d05b81 100644 --- a/lib/widgets/attachment/attachment_item.dart +++ b/lib/widgets/attachment/attachment_item.dart @@ -1,25 +1,55 @@ +import 'package:dismissible_page/dismissible_page.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/types/attachment.dart'; +import 'package:surface/widgets/attachment/attachment_detail.dart'; import 'package:surface/widgets/universal_image.dart'; +import 'package:uuid/uuid.dart'; class AttachmentItem extends StatelessWidget { final SnAttachment data; - const AttachmentItem({super.key, required this.data}); + final bool isExpandable; + const AttachmentItem({ + super.key, + required this.data, + this.isExpandable = false, + }); - @override - Widget build(BuildContext context) { + Widget _buildContent(BuildContext context, String heroTag) { final tp = data.mimetype.split('/').firstOrNull; final sn = context.read(); switch (tp) { case 'image': - return UniversalImage( - sn.getAttachmentUrl(data.rid), - fit: BoxFit.cover, + return Hero( + tag: 'attachment-${data.rid}-$heroTag', + child: UniversalImage( + sn.getAttachmentUrl(data.rid), + fit: BoxFit.cover, + ), ); default: return const Placeholder(); } } + + @override + Widget build(BuildContext context) { + final uuid = Uuid(); + final heroTag = uuid.v4(); + + if (isExpandable) { + return GestureDetector( + child: _buildContent(context, heroTag), + onTap: () { + context.pushTransparentRoute( + AttachmentDetailPopup(data: data, heroTag: heroTag), + rootNavigator: true, + ); + }, + ); + } + + return _buildContent(context, heroTag); + } } diff --git a/lib/widgets/attachment/attachment_list.dart b/lib/widgets/attachment/attachment_list.dart index 271d931..c974094 100644 --- a/lib/widgets/attachment/attachment_list.dart +++ b/lib/widgets/attachment/attachment_list.dart @@ -1,6 +1,9 @@ +import 'dart:math' as math; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:responsive_framework/responsive_framework.dart'; import 'package:surface/types/attachment.dart'; import 'package:surface/widgets/attachment/attachment_item.dart'; @@ -15,6 +18,10 @@ class AttachmentList extends StatelessWidget { this.maxListHeight, }); + static const double kMaxListItemWidth = 520; + static const BorderRadius kDefaultRadius = + BorderRadius.all(Radius.circular(8)); + @override Widget build(BuildContext context) { final borderSide = (bordered ?? false) @@ -23,11 +30,36 @@ class AttachmentList extends StatelessWidget { if (data.isEmpty) return const SizedBox.shrink(); if (data.length == 1) { + if (ResponsiveBreakpoints.of(context).largerThan(MOBILE)) { + return Container( + constraints: BoxConstraints( + maxWidth: math.min( + MediaQuery.of(context).size.width - 20, + kMaxListItemWidth, + ), + ), + decoration: BoxDecoration( + border: Border(top: borderSide, bottom: borderSide), + borderRadius: kDefaultRadius, + ), + child: AspectRatio( + aspectRatio: data[0].metadata['ratio']?.toDouble() ?? 1, + child: ClipRRect( + borderRadius: kDefaultRadius, + child: AttachmentItem(data: data[0], isExpandable: true), + ), + ), + ); + } + return Container( decoration: BoxDecoration( border: Border(top: borderSide, bottom: borderSide), ), - child: AttachmentItem(data: data[0]), + child: AspectRatio( + aspectRatio: data[0].metadata['ratio']?.toDouble() ?? 1, + child: AttachmentItem(data: data[0], isExpandable: true), + ), ); } @@ -39,20 +71,22 @@ class AttachmentList extends StatelessWidget { shrinkWrap: true, itemCount: data.length, itemBuilder: (context, idx) { - const radius = BorderRadius.all(Radius.circular(8)); return Container( constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width - 20, + maxWidth: math.min( + MediaQuery.of(context).size.width - 20, + kMaxListItemWidth, + ), ), decoration: BoxDecoration( border: Border(top: borderSide, bottom: borderSide), - borderRadius: radius, + borderRadius: kDefaultRadius, ), child: AspectRatio( aspectRatio: data[idx].metadata['ratio']?.toDouble() ?? 1, child: ClipRRect( - borderRadius: radius, - child: AttachmentItem(data: data[idx]), + borderRadius: kDefaultRadius, + child: AttachmentItem(data: data[idx], isExpandable: true), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 1ecf56b..8be4507 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -334,6 +334,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + dismissible_page: + dependency: "direct main" + description: + name: dismissible_page + sha256: "5b2316f770fe83583f770df1f6505cb19102081c5971979806e77f2e507a9958" + url: "https://pub.dev" + source: hosted + version: "1.0.2" dropdown_button2: dependency: "direct main" description: @@ -1034,6 +1042,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" + url: "https://pub.dev" + source: hosted + version: "0.15.0" platform: dependency: transitive description: @@ -1416,7 +1432,7 @@ packages: source: hosted version: "3.1.3" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff diff --git a/pubspec.yaml b/pubspec.yaml index b2ba9ef..4503c3b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -67,6 +67,9 @@ dependencies: flutter_expandable_fab: ^2.3.0 dropdown_button2: ^2.3.9 flutter_context_menu: ^0.2.0 + dismissible_page: ^1.0.2 + uuid: ^4.5.1 + photo_view: ^0.15.0 dev_dependencies: flutter_test: