Share via image

This commit is contained in:
LittleSheep 2024-10-13 23:12:23 +08:00
parent 6c32d76f78
commit ad7a34ec18
12 changed files with 165 additions and 19 deletions

View File

@ -482,5 +482,7 @@
"authPreferencesDesc": "Set the security behavior of your account", "authPreferencesDesc": "Set the security behavior of your account",
"authMaximumAuthSteps": "Maximum authentication steps", "authMaximumAuthSteps": "Maximum authentication steps",
"authMaximumAuthStepsDesc": "The maximum number of authentication steps when logging in, higher value is more secure, lower value is more convenient; default is 2", "authMaximumAuthStepsDesc": "The maximum number of authentication steps when logging in, higher value is more secure, lower value is more convenient; default is 2",
"auditLog": "Audit log" "auditLog": "Audit log",
"shareImage": "Share as image",
"shareImageFooter": "See more interesting posts on Solar Network"
} }

View File

@ -478,5 +478,7 @@
"authPreferencesDesc": "调整账号的安全行为模式", "authPreferencesDesc": "调整账号的安全行为模式",
"authMaximumAuthSteps": "最大认证步数", "authMaximumAuthSteps": "最大认证步数",
"authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2", "authMaximumAuthStepsDesc": "登陆时最多的验证步数,值越高则越安全,反之则会相对方便;默认设置为 2",
"auditLog": "活动日志" "auditLog": "活动日志",
"shareImage": "分享图片",
"shareImageFooter": "上 Solar Network 看更多有趣帖子"
} }

View File

@ -21,8 +21,7 @@ class AuditLogScreen extends StatefulWidget {
class _AuditLogScreenState extends State<AuditLogScreen> { class _AuditLogScreenState extends State<AuditLogScreen> {
bool _isBusy = true; bool _isBusy = true;
int _totalEvent = 0; final List<AuditEvent> _events = List.empty(growable: true);
List<AuditEvent> _events = List.empty(growable: true);
Future<void> _getEvents() async { Future<void> _getEvents() async {
if (!_isBusy) setState(() => _isBusy = true); if (!_isBusy) setState(() => _isBusy = true);
@ -38,7 +37,6 @@ class _AuditLogScreenState extends State<AuditLogScreen> {
final result = PaginationResult.fromJson(resp.body); final result = PaginationResult.fromJson(resp.body);
setState(() { setState(() {
_totalEvent = result.count;
_events.addAll( _events.addAll(
result.data?.map((x) => AuditEvent.fromJson(x)).toList() ?? result.data?.map((x) => AuditEvent.fromJson(x)).toList() ??
List.empty(), List.empty(),

View File

@ -588,8 +588,6 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
color: color:
Theme.of(context).colorScheme.surfaceContainerLow, Theme.of(context).colorScheme.surfaceContainerLow,
child: PostListEntryWidget( child: PostListEntryWidget(
backgroundColor:
Theme.of(context).colorScheme.surfaceContainerLow,
item: element, item: element,
isClickable: true, isClickable: true,
isNestedClickable: true, isNestedClickable: true,

View File

@ -393,9 +393,6 @@ class _DashboardScreenState extends State<DashboardScreen> {
vertical: 8, vertical: 8,
horizontal: 4, horizontal: 4,
), ),
backgroundColor: Theme.of(context)
.colorScheme
.surfaceContainerLow,
), ),
), ),
), ),

View File

@ -160,13 +160,13 @@ class _ExploreScreenState extends State<ExploreScreen>
toolbarHeight: AppTheme.toolbarHeight(context), toolbarHeight: AppTheme.toolbarHeight(context),
leading: AppBarLeadingButton.adaptive(context), leading: AppBarLeadingButton.adaptive(context),
actions: [ actions: [
const BackgroundStateWidget(),
IconButton( IconButton(
icon: const Icon(Icons.search), icon: const Icon(Icons.search),
onPressed: () { onPressed: () {
AppRouter.instance.pushNamed('postSearch'); AppRouter.instance.pushNamed('postSearch');
}, },
), ),
const BackgroundStateWidget(),
const NotificationButton(), const NotificationButton(),
SizedBox( SizedBox(
width: AppTheme.isLargeScreen(context) ? 8 : 16, width: AppTheme.isLargeScreen(context) ? 8 : 16,

View File

@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/firebase_analytics.dart';
@ -5,6 +6,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:screenshot/screenshot.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
@ -12,6 +15,7 @@ import 'package:solian/platform.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/posts/post_editor.dart'; import 'package:solian/screens/posts/post_editor.dart';
import 'package:solian/widgets/posts/post_share.dart';
import 'package:solian/widgets/reports/abuse_report.dart'; import 'package:solian/widgets/reports/abuse_report.dart';
class PostAction extends StatefulWidget { class PostAction extends StatefulWidget {
@ -84,6 +88,24 @@ class _PostActionState extends State<PostAction> {
} }
} }
Future<void> _shareImage() async {
final screenshot = ScreenshotController();
final image = await screenshot.captureFromWidget(
PostShareImage(item: widget.item),
context: context,
);
final directory = await getApplicationDocumentsDirectory();
final imageFile = await File(
'${directory.path}/temporary_share_image.png',
).create();
await imageFile.writeAsBytes(image);
final file = XFile(imageFile.path);
await Share.shareXFiles([file]);
await imageFile.delete();
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -135,16 +157,29 @@ class _PostActionState extends State<PostAction> {
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.share), leading: const Icon(Icons.share),
title: Text('share'.tr), title: Text('share'.tr),
trailing: PlatformInfo.isIOS || PlatformInfo.isAndroid trailing: Row(
? IconButton( mainAxisSize: MainAxisSize.min,
children: [
if (PlatformInfo.isIOS || PlatformInfo.isAndroid)
IconButton(
icon: const Icon(Icons.link_off), icon: const Icon(Icons.link_off),
tooltip: 'shareNoUri'.tr, tooltip: 'shareNoUri'.tr,
onPressed: () async { onPressed: () async {
await _doShare(noUri: true); await _doShare(noUri: true);
Navigator.pop(context); Navigator.pop(context);
}, },
) ),
: null, if (PlatformInfo.isIOS || PlatformInfo.isAndroid)
IconButton(
icon: const Icon(Icons.image),
tooltip: 'shareImage'.tr,
onPressed: () async {
await _shareImage();
Navigator.pop(context);
},
),
],
),
onTap: () async { onTap: () async {
await _doShare(); await _doShare();
Navigator.pop(context); Navigator.pop(context);

View File

@ -41,7 +41,6 @@ class PostListWidget extends StatelessWidget {
isClickable: isClickable, isClickable: isClickable,
showFeaturedReply: true, showFeaturedReply: true,
item: item, item: item,
backgroundColor: backgroundColor,
onUpdate: () { onUpdate: () {
controller.refresh(); controller.refresh();
}, },
@ -60,7 +59,6 @@ class PostListEntryWidget extends StatelessWidget {
final bool isClickable; final bool isClickable;
final bool showFeaturedReply; final bool showFeaturedReply;
final Post item; final Post item;
final Color? backgroundColor;
final EdgeInsets? padding; final EdgeInsets? padding;
final Function onUpdate; final Function onUpdate;
@ -71,7 +69,6 @@ class PostListEntryWidget extends StatelessWidget {
required this.isClickable, required this.isClickable,
required this.showFeaturedReply, required this.showFeaturedReply,
required this.item, required this.item,
this.backgroundColor,
this.padding, this.padding,
required this.onUpdate, required this.onUpdate,
}); });

View File

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:solian/models/post.dart';
import 'package:solian/widgets/posts/post_item.dart';
import 'package:solian/widgets/root_container.dart';
class PostShareImage extends StatelessWidget {
final Post item;
const PostShareImage({super.key, required this.item});
@override
Widget build(BuildContext context) {
final textColor = Theme.of(context).colorScheme.onSurface.withOpacity(0.3);
return RootContainer(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Gap(8),
Material(
color: Colors.transparent,
child: Card(
child: PostItem(
item: item,
isShowEmbed: true,
isClickable: false,
showFeaturedReply: false,
isReactable: false,
isShowReply: false,
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
onComment: () {},
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Image.asset(
'assets/logo.png',
width: 48,
height: 48,
),
),
const Gap(16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'shareImageFooter'.tr,
style: TextStyle(
fontSize: 13,
color: textColor,
),
),
Text(
'Solsynth LLC © ${DateTime.now().year}',
style: TextStyle(
fontSize: 11,
color: textColor,
),
),
],
),
],
),
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Material(
color: Theme.of(context).colorScheme.surface,
child: QrImageView(
data: 'https://solsynth.dev/posts/${item.id}',
version: QrVersions.auto,
padding: const EdgeInsets.all(4),
size: 48,
),
),
),
],
),
],
).paddingSymmetric(horizontal: 36, vertical: 24),
);
}
}

View File

@ -25,7 +25,6 @@ class PostSingleDisplay extends StatelessWidget {
isNestedClickable: true, isNestedClickable: true,
showFeaturedReply: true, showFeaturedReply: true,
onUpdate: onUpdate, onUpdate: onUpdate,
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
), ),
), ),
), ),

View File

@ -1685,6 +1685,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
qr:
dependency: transitive
description:
name: qr
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
qr_flutter:
dependency: "direct main"
description:
name: qr_flutter
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
url: "https://pub.dev"
source: hosted
version: "4.1.0"
recase: recase:
dependency: transitive dependency: transitive
description: description:
@ -1757,6 +1773,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.3" version: "0.1.3"
screenshot:
dependency: "direct main"
description:
name: screenshot
sha256: "63817697a7835e6ce82add4228e15d233b74d42975c143ad8cfe07009fab866b"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sdp_transform: sdp_transform:
dependency: transitive dependency: transitive
description: description:

View File

@ -85,6 +85,8 @@ dependencies:
syntax_highlight: ^0.4.0 syntax_highlight: ^0.4.0
flutter_udid: ^3.0.0 flutter_udid: ^3.0.0
timeline_tile: ^2.0.0 timeline_tile: ^2.0.0
screenshot: ^3.0.0
qr_flutter: ^4.1.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: