Compare commits

..

No commits in common. "e8384338f8a78f344cf97b7f076139b153a6b165" and "9588fc04756aad1afae37d495e70893bf885b0a2" have entirely different histories.

7 changed files with 160 additions and 362 deletions

View File

@ -351,7 +351,5 @@
"friendRequestAccept": "Accept", "friendRequestAccept": "Accept",
"friendRequestDecline": "Decline", "friendRequestDecline": "Decline",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unsubscribe": "Unsubscribe", "unsubscribe": "Unsubscribe"
"attachmentUploadBy": "Upload by",
"attachmentShotOn": "Shot on {}"
} }

View File

@ -351,7 +351,5 @@
"friendRequestAccept": "接受", "friendRequestAccept": "接受",
"friendRequestDecline": "拒绝", "friendRequestDecline": "拒绝",
"subscribe": "订阅", "subscribe": "订阅",
"unsubscribe": "取消订阅", "unsubscribe": "取消订阅"
"attachmentUploadBy": "上传者",
"attachmentShotOn": "由 {} 拍摄"
} }

View File

@ -81,9 +81,9 @@ class SolianApp extends StatelessWidget {
// Data layer // Data layer
Provider(create: (_) => SnNetworkProvider()), Provider(create: (_) => SnNetworkProvider()),
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
Provider(create: (ctx) => SnAttachmentProvider(ctx)), Provider(create: (ctx) => SnAttachmentProvider(ctx)),
Provider(create: (ctx) => SnPostContentProvider(ctx)), Provider(create: (ctx) => SnPostContentProvider(ctx)),
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
Provider(create: (ctx) => SnRelationshipProvider(ctx)), Provider(create: (ctx) => SnRelationshipProvider(ctx)),
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)), ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)), ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),

View File

@ -2,17 +2,14 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:surface/providers/sn_attachment.dart'; import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/post.dart'; import 'package:surface/types/post.dart';
class SnPostContentProvider { class SnPostContentProvider {
late final SnNetworkProvider _sn; late final SnNetworkProvider _sn;
late final UserDirectoryProvider _ud;
late final SnAttachmentProvider _attach; late final SnAttachmentProvider _attach;
SnPostContentProvider(BuildContext context) { SnPostContentProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>(); _sn = context.read<SnNetworkProvider>();
_ud = context.read<UserDirectoryProvider>();
_attach = context.read<SnAttachmentProvider>(); _attach = context.read<SnAttachmentProvider>();
} }
@ -40,13 +37,6 @@ class SnPostContentProvider {
); );
} }
await _ud.listAccount(
attachments
.where((ele) => ele != null)
.map((ele) => ele!.accountId)
.toSet(),
);
return out; return out;
} }

View File

@ -1,16 +1,10 @@
import 'package:dismissible_page/dismissible_page.dart'; import 'package:dismissible_page/dismissible_page.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart'; import 'package:photo_view/photo_view_gallery.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/attachment.dart'; import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/account/account_image.dart';
import 'package:surface/widgets/universal_image.dart'; import 'package:surface/widgets/universal_image.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -33,37 +27,17 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
late final PageController _pageController = late final PageController _pageController =
PageController(initialPage: widget.initialIndex ?? 0); PageController(initialPage: widget.initialIndex ?? 0);
void _updatePage() {
setState(() {});
}
@override
void initState() {
super.initState();
_pageController.addListener(_updatePage);
}
@override @override
void dispose() { void dispose() {
_pageController.removeListener(_updatePage);
_pageController.dispose(); _pageController.dispose();
super.dispose(); super.dispose();
} }
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sn = context.read<SnNetworkProvider>(); final sn = context.read<SnNetworkProvider>();
final uuid = Uuid(); final uuid = Uuid();
final metaTextStyle = GoogleFonts.roboto(
fontSize: 12,
color: _unFocusColor,
height: 1,
);
return DismissiblePage( return DismissiblePage(
onDismissed: () { onDismissed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -71,212 +45,51 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
direction: DismissiblePageDismissDirection.down, direction: DismissiblePageDismissDirection.down,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
isFullScreen: true, isFullScreen: true,
child: Stack( child: Builder(builder: (context) {
children: [ if (widget.data.length == 1) {
Builder(builder: (context) { final heroTag = widget.heroTags?.first ?? uuid.v4();
if (widget.data.length == 1) { return Hero(
final heroTag = widget.heroTags?.first ?? uuid.v4(); tag: 'attachment-${widget.data.first.rid}-$heroTag',
return Hero( child: PhotoView(
tag: 'attachment-${widget.data.first.rid}-$heroTag', key: Key('attachment-detail-${widget.data.first.rid}-$heroTag'),
child: PhotoView(
key: Key(
'attachment-detail-${widget.data.first.rid}-$heroTag'),
backgroundDecoration:
BoxDecoration(color: Colors.transparent),
imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.first.rid),
),
),
);
}
return PhotoViewGallery.builder(
pageController: _pageController,
scrollPhysics: const BouncingScrollPhysics(),
builder: (context, idx) {
final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4();
return PhotoViewGalleryPageOptions(
imageProvider: UniversalImage.provider(
sn.getAttachmentUrl(widget.data.elementAt(idx).rid),
),
heroAttributes: PhotoViewHeroAttributes(
tag: 'attachment-${widget.data.first.rid}-$heroTag',
),
);
},
itemCount: widget.data.length,
loadingBuilder: (context, event) => Center(
child: SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator(
value: event == null
? 0
: event.cumulativeBytesLoaded /
(event.expectedTotalBytes ?? 1),
),
),
),
backgroundDecoration: BoxDecoration(color: Colors.transparent), backgroundDecoration: BoxDecoration(color: Colors.transparent),
); imageProvider: UniversalImage.provider(
}), sn.getAttachmentUrl(widget.data.first.rid),
Align(
alignment: Alignment.bottomCenter,
child: IgnorePointer(
child: Container(
height: 300,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Theme.of(context).colorScheme.surface,
Colors.transparent,
],
),
),
), ),
), ),
), );
Positioned( }
left: 16,
right: 16,
bottom: MediaQuery.of(context).padding.bottom > 16
? -MediaQuery.of(context).padding.bottom
: 16,
child: SizedBox(
height: 180,
child: Material(
color: Colors.transparent,
child: Builder(builder: (context) {
final ud = context.read<UserDirectoryProvider>();
final item = widget.data.elementAt(
_pageController.page?.round() ?? 0,
);
final account = ud.getAccountFromCache(item.accountId);
return Column( return PhotoViewGallery.builder(
crossAxisAlignment: CrossAxisAlignment.start, pageController: _pageController,
children: [ scrollPhysics: const BouncingScrollPhysics(),
if (item.accountId > 0) builder: (context, idx) {
Row( final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4();
children: [ return PhotoViewGalleryPageOptions(
IgnorePointer( imageProvider: UniversalImage.provider(
child: AccountImage( sn.getAttachmentUrl(widget.data.elementAt(idx).rid),
content: account!.avatar, ),
radius: 19, heroAttributes: PhotoViewHeroAttributes(
), tag: 'attachment-${widget.data.first.rid}-$heroTag',
), ),
const Gap(8), );
Expanded( },
child: IgnorePointer( itemCount: widget.data.length,
child: Column( loadingBuilder: (context, event) => Center(
crossAxisAlignment: CrossAxisAlignment.start, child: SizedBox(
children: [ width: 20.0,
Text( height: 20.0,
'attachmentUploadBy'.tr(), child: CircularProgressIndicator(
style: value: event == null
Theme.of(context).textTheme.bodySmall, ? 0
), : event.cumulativeBytesLoaded /
Text( (event.expectedTotalBytes ?? 1),
account.nick,
style: Theme.of(context)
.textTheme
.bodyMedium,
),
],
),
),
),
if (widget.data.length > 1)
IgnorePointer(
child: Text(
'${(_pageController.page?.round() ?? 0) + 1}/${widget.data.length}',
style: GoogleFonts.robotoMono(fontSize: 13),
).padding(right: 8),
),
],
),
const Gap(4),
IgnorePointer(
child: Text(
item.alt,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
),
),
),
const Gap(2),
IgnorePointer(
child: Wrap(
spacing: 6,
children: [
if (item.metadata['exif'] == null)
Text(
'#${item.rid}',
style: metaTextStyle,
),
if (item.metadata['exif']?['Model'] != null)
Text(
'attachmentShotOn'.tr(args: [
item.metadata['exif']?['Model'],
]),
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['ShutterSpeed'] != null)
Text(
item.metadata['exif']?['ShutterSpeed'],
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['ISO'] != null)
Text(
'ISO${item.metadata['exif']?['ISO']}',
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['Aperture'] != null)
Text(
'f/${item.metadata['exif']?['Aperture']}',
style: metaTextStyle,
).padding(right: 2),
if (item.metadata['exif']?['Megapixels'] != null &&
item.metadata['exif']?['Model'] != null)
Text(
'${item.metadata['exif']?['Megapixels']}MP',
style: metaTextStyle,
)
else
Text(
'${item.size} Bytes',
style: metaTextStyle,
),
Text(
'${item.metadata['width']}x${item.metadata['height']}',
style: metaTextStyle,
),
if (item.metadata['ratio'] != null)
Text(
(item.metadata['ratio'] as num)
.toStringAsFixed(2),
style: metaTextStyle,
),
Text(
item.mimetype,
style: metaTextStyle,
),
],
),
),
],
);
}),
), ),
), ),
), ),
], backgroundDecoration: BoxDecoration(color: Colors.transparent),
), );
}),
); );
} }
} }

View File

@ -38,141 +38,140 @@ class _AttachmentListState extends State<AttachmentList> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final aspectRatio = widget.data[0]?.metadata['ratio']?.toDouble() ?? 1; final borderSide = widget.bordered
? BorderSide(width: 1, color: Theme.of(context).dividerColor)
: BorderSide.none;
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer;
final constraints = BoxConstraints(
minWidth: 80,
maxHeight: widget.maxHeight ?? double.infinity,
);
return LayoutBuilder( if (widget.data.isEmpty) return const SizedBox.shrink();
builder: (context, layoutConstraints) { if (widget.data.length == 1) {
final borderSide = widget.bordered return GestureDetector(
? BorderSide(width: 1, color: Theme.of(context).dividerColor) child: Builder(
: BorderSide.none; builder: (context) {
final backgroundColor = Theme.of(context).colorScheme.surfaceContainer; if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) ||
final constraints = BoxConstraints( widget.noGrow) {
minWidth: 80, return Padding(
maxWidth: layoutConstraints.maxWidth - 20, // Single child list-like displaying
maxHeight: widget.maxHeight ?? double.infinity, padding: widget.listPadding ?? EdgeInsets.zero,
); child: Container(
constraints: constraints,
if (widget.data.isEmpty) return const SizedBox.shrink(); decoration: BoxDecoration(
if (widget.data.length == 1) { color: backgroundColor,
return AspectRatio( border: Border(top: borderSide, bottom: borderSide),
aspectRatio: widget.data[0]?.metadata['ratio']?.toDouble() ?? borderRadius: AttachmentList.kDefaultRadius,
switch (widget.data[0]?.mimetype.split('/').firstOrNull) { ),
'audio' => 16 / 9, child: AspectRatio(
'video' => 16 / 9, aspectRatio: widget.data[0]?.metadata['ratio']
_ => 1, ?.toDouble() ??
}, switch (
child: GestureDetector( widget.data[0]?.mimetype.split('/').firstOrNull) {
child: Builder( 'audio' => 16 / 9,
builder: (context) { 'video' => 16 / 9,
if (ResponsiveBreakpoints.of(context).largerThan(MOBILE) || _ => 1,
widget.noGrow) { },
return Padding( child: ClipRRect(
// Single child list-like displaying borderRadius: AttachmentList.kDefaultRadius,
padding: widget.listPadding ?? EdgeInsets.zero, child: AttachmentItem(
child: Container( data: widget.data[0],
constraints: constraints, heroTag: heroTags[0],
decoration: BoxDecoration(
color: backgroundColor,
border: Border(top: borderSide, bottom: borderSide),
borderRadius: AttachmentList.kDefaultRadius,
),
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(
data: widget.data[0],
heroTag: heroTags[0],
),
),
), ),
); ),
} ),
),
);
}
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor, color: backgroundColor,
border: Border(top: borderSide, bottom: borderSide), border: Border(top: borderSide, bottom: borderSide),
),
child: AttachmentItem(
data: widget.data[0],
heroTag: heroTags.first,
),
);
},
), ),
child: AspectRatio(
aspectRatio: widget.data[0]?.metadata['ratio']?.toDouble() ?? 1,
child: AttachmentItem(
data: widget.data[0],
heroTag: heroTags.first,
),
),
);
},
),
onTap: () {
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
initialIndex: 0,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
);
}
return Container(
constraints: BoxConstraints(maxHeight: widget.maxHeight ?? 320),
child: ScrollConfiguration(
behavior: _AttachmentListScrollBehavior(),
child: ListView.separated(
shrinkWrap: true,
itemCount: widget.data.length,
itemBuilder: (context, idx) {
return GestureDetector(
onTap: () { onTap: () {
context.pushTransparentRoute( context.pushTransparentRoute(
AttachmentZoomView( AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(), data: widget.data.where((ele) => ele != null).cast(),
initialIndex: 0, initialIndex: idx,
heroTags: heroTags, heroTags: heroTags,
), ),
backgroundColor: Colors.black.withOpacity(0.7), backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true, rootNavigator: true,
); );
}, },
), child: Stack(
); children: [
} Container(
constraints: constraints,
return AspectRatio( decoration: BoxDecoration(
aspectRatio: aspectRatio, color: backgroundColor,
child: Container( border: Border(top: borderSide, bottom: borderSide),
constraints: BoxConstraints(maxHeight: widget.maxHeight ?? 320), borderRadius: AttachmentList.kDefaultRadius,
child: ScrollConfiguration(
behavior: _AttachmentListScrollBehavior(),
child: ListView.separated(
shrinkWrap: true,
itemCount: widget.data.length,
itemBuilder: (context, idx) {
return GestureDetector(
onTap: () {
context.pushTransparentRoute(
AttachmentZoomView(
data: widget.data.where((ele) => ele != null).cast(),
initialIndex: idx,
heroTags: heroTags,
),
backgroundColor: Colors.black.withOpacity(0.7),
rootNavigator: true,
);
},
child: Stack(
children: [
Container(
constraints: constraints,
decoration: BoxDecoration(
color: backgroundColor,
border: Border(top: borderSide, bottom: borderSide),
borderRadius: AttachmentList.kDefaultRadius,
),
child: ClipRRect(
borderRadius: AttachmentList.kDefaultRadius,
child: AttachmentItem(
data: widget.data[idx],
heroTag: heroTags[idx],
),
),
),
Positioned(
right: 8,
bottom: 12,
child: Chip(
label: Text('${idx + 1}/${widget.data.length}'),
),
),
],
), ),
); child: AspectRatio(
}, aspectRatio:
separatorBuilder: (context, index) => const Gap(8), widget.data[idx]?.metadata['ratio']?.toDouble() ?? 1,
padding: widget.listPadding, child: ClipRRect(
physics: const BouncingScrollPhysics(), borderRadius: AttachmentList.kDefaultRadius,
scrollDirection: Axis.horizontal, child: AttachmentItem(
data: widget.data[idx],
heroTag: heroTags[idx],
),
),
),
),
Positioned(
right: 12,
bottom: 12,
child: Chip(
label: Text('${idx + 1}/${widget.data.length}'),
),
),
],
), ),
), );
), },
); separatorBuilder: (context, index) => const Gap(8),
}, padding: widget.listPadding,
physics: const BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
),
),
); );
} }
} }

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 2.0.0+13 version: 2.0.0+12
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4