mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +00:00
fix #155
This commit is contained in:
parent
597ba72c1e
commit
30a3668eec
5 changed files with 145 additions and 46 deletions
|
|
@ -56,6 +56,19 @@ Future<String?> saveImageToGallery(Uint8List imageBytes) async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<String?> saveVideoToGallery(String videoPath) async {
|
||||
final hasAccess = await Gal.hasAccess();
|
||||
if (!hasAccess) {
|
||||
await Gal.requestAccess();
|
||||
}
|
||||
try {
|
||||
await Gal.putVideo(videoPath);
|
||||
return null;
|
||||
} on GalException catch (e) {
|
||||
return e.type.message;
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List getRandomUint8List(int length) {
|
||||
final Random random = Random.secure();
|
||||
final Uint8List randomBytes = Uint8List(length);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'dart:typed_data';
|
||||
|
|
@ -7,12 +8,13 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
class SaveToGalleryButton extends StatefulWidget {
|
||||
final Future<Uint8List?> Function() getMergedImage;
|
||||
final String? sendNextMediaToUserName;
|
||||
final XFile? videoFilePath;
|
||||
|
||||
const SaveToGalleryButton({
|
||||
super.key,
|
||||
const SaveToGalleryButton(
|
||||
{super.key,
|
||||
required this.getMergedImage,
|
||||
this.sendNextMediaToUserName,
|
||||
});
|
||||
this.videoFilePath});
|
||||
|
||||
@override
|
||||
State<SaveToGalleryButton> createState() => SaveToGalleryButtonState();
|
||||
|
|
@ -37,15 +39,31 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
|||
setState(() {
|
||||
_imageSaving = true;
|
||||
});
|
||||
|
||||
String? res;
|
||||
|
||||
if (widget.videoFilePath != null) {
|
||||
res = await saveVideoToGallery(widget.videoFilePath!.path);
|
||||
} else {
|
||||
Uint8List? imageBytes = await widget.getMergedImage();
|
||||
if (imageBytes == null || !context.mounted) return;
|
||||
final res = await saveImageToGallery(imageBytes);
|
||||
res = await saveImageToGallery(imageBytes);
|
||||
}
|
||||
if (res == null) {
|
||||
setState(() {
|
||||
_imageSaving = false;
|
||||
_imageSaved = true;
|
||||
});
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(res),
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
setState(() {
|
||||
_imageSaving = false;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
|
|||
|
|
@ -501,6 +501,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
children: [
|
||||
SaveToGalleryButton(
|
||||
getMergedImage: getMergedImage,
|
||||
videoFilePath: widget.videoFilePath,
|
||||
sendNextMediaToUserName: sendNextMediaToUserName,
|
||||
),
|
||||
if (sendNextMediaToUserName != null) SizedBox(width: 10),
|
||||
|
|
|
|||
|
|
@ -23,32 +23,29 @@ import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
|||
import 'package:twonly/src/views/chats/media_viewer_view.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/contact/contact_view.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
InputDecoration inputTextMessageDeco(BuildContext context) {
|
||||
return InputDecoration(
|
||||
hintText: context.lang.chatListDetailInput,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0),
|
||||
class ChatMediaViewerFullScreen extends StatelessWidget {
|
||||
const ChatMediaViewerFullScreen({super.key, required this.message});
|
||||
final Message message;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: InChatMediaViewer(message: message, isInFullscreen: true),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderSide: BorderSide(color: Colors.grey, width: 2.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InChatMediaViewer extends StatefulWidget {
|
||||
const InChatMediaViewer({super.key, required this.message});
|
||||
const InChatMediaViewer(
|
||||
{super.key, required this.message, this.isInFullscreen = false});
|
||||
|
||||
final Message message;
|
||||
final bool isInFullscreen;
|
||||
|
||||
@override
|
||||
State<InChatMediaViewer> createState() => _InChatMediaViewerState();
|
||||
|
|
@ -58,6 +55,8 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
|||
File? image;
|
||||
File? video;
|
||||
bool isMounted = true;
|
||||
bool mirrorVideo = false;
|
||||
VideoPlayerController? videoController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -73,7 +72,26 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
|||
isSend ? "send" : "received",
|
||||
);
|
||||
if (!isMounted) return;
|
||||
final videoPath = File("$basePath.mp4");
|
||||
final imagePath = File("$basePath.png");
|
||||
if (videoPath.existsSync() && widget.message.contentJson != null) {
|
||||
MessageContent? content = MessageContent.fromJson(
|
||||
MessageKind.media, jsonDecode(widget.message.contentJson!));
|
||||
if (content is MediaMessageContent) {
|
||||
mirrorVideo = content.mirrorVideo;
|
||||
}
|
||||
videoController = VideoPlayerController.file(videoPath);
|
||||
videoController?.initialize().then((_) {
|
||||
if (!widget.isInFullscreen) {
|
||||
videoController!.setVolume(0);
|
||||
}
|
||||
videoController!.play();
|
||||
});
|
||||
|
||||
setState(() {
|
||||
image = imagePath;
|
||||
});
|
||||
}
|
||||
if (imagePath.existsSync()) {
|
||||
setState(() {
|
||||
image = imagePath;
|
||||
|
|
@ -87,13 +105,31 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
|||
void dispose() {
|
||||
super.dispose();
|
||||
isMounted = false;
|
||||
videoController?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (widget.isInFullscreen) return;
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) {
|
||||
return ChatMediaViewerFullScreen(message: widget.message);
|
||||
}),
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
if (image != null) Image.file(image!),
|
||||
if (videoController != null)
|
||||
Positioned.fill(
|
||||
child: Transform.flip(
|
||||
flipX: mirrorVideo,
|
||||
child: VideoPlayer(videoController!),
|
||||
),
|
||||
),
|
||||
if (image == null && video == null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
|
|
@ -103,6 +139,7 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
|
|||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -650,3 +687,24 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
InputDecoration inputTextMessageDeco(BuildContext context) {
|
||||
return InputDecoration(
|
||||
hintText: context.lang.chatListDetailInput,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.primary, width: 2.0),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(20.0),
|
||||
borderSide: BorderSide(color: Colors.grey, width: 2.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
|
||||
// current image related
|
||||
Uint8List? imageBytes;
|
||||
String? videoPath;
|
||||
VideoPlayerController? videoController;
|
||||
|
||||
DateTime? canBeSeenUntil;
|
||||
|
|
@ -137,6 +138,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
imageSaved = false;
|
||||
mirrorVideo = false;
|
||||
progress = 0;
|
||||
videoPath = null;
|
||||
isDownloading = false;
|
||||
isRealTwonly = false;
|
||||
showSendTextMessageInput = false;
|
||||
|
|
@ -200,9 +202,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
);
|
||||
|
||||
if (content.isVideo) {
|
||||
final vidoePath = await getVideoPath(current.messageId);
|
||||
if (vidoePath != null) {
|
||||
videoController = VideoPlayerController.file(File(vidoePath.path));
|
||||
final videoPathTmp = await getVideoPath(current.messageId);
|
||||
if (videoPathTmp != null) {
|
||||
videoController = VideoPlayerController.file(File(videoPathTmp.path));
|
||||
videoController?.setLooping(content.maxShowTime == gMediaShowInfinite);
|
||||
videoController?.initialize().then((_) {
|
||||
videoController!.play();
|
||||
|
|
@ -214,7 +216,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
}
|
||||
});
|
||||
}
|
||||
setState(() {});
|
||||
setState(() {
|
||||
videoPath = videoPathTmp.path;
|
||||
});
|
||||
}).catchError((Object error) {
|
||||
Logger("media_viewer_view.dart").shout(error);
|
||||
});
|
||||
|
|
@ -307,9 +311,14 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
imageSaved = true;
|
||||
});
|
||||
final user = await getUser();
|
||||
|
||||
if (user != null && (user.storeMediaFilesInGallery ?? true)) {
|
||||
if (videoPath != null) {
|
||||
await saveVideoToGallery(videoPath!);
|
||||
} else {
|
||||
await saveImageToGallery(imageBytes!);
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
imageSaving = false;
|
||||
});
|
||||
|
|
@ -320,7 +329,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (maxShowTime == gMediaShowInfinite && videoController == null)
|
||||
if (maxShowTime == gMediaShowInfinite)
|
||||
OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(
|
||||
iconColor: imageSaved
|
||||
|
|
|
|||
Loading…
Reference in a new issue