mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +00:00
start with #190
This commit is contained in:
parent
4b84b3f20e
commit
eea77a6f08
14 changed files with 410 additions and 323 deletions
|
|
@ -81,11 +81,11 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
|||
.get();
|
||||
}
|
||||
|
||||
Future<List<Message>> getAllStoredMediaFiles() {
|
||||
Stream<List<Message>> getAllStoredMediaFiles() {
|
||||
return (select(messages)
|
||||
..where((t) => t.mediaStored.equals(true))
|
||||
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
|
||||
.get();
|
||||
.watch();
|
||||
}
|
||||
|
||||
Future<List<Message>> getAllMessagesPendingUploadOlderThanAMinute() {
|
||||
|
|
|
|||
|
|
@ -232,5 +232,7 @@
|
|||
"additionalUsersPlusTokens": "twonly-Codes für \"Plus\"-Benutzer",
|
||||
"additionalUsersFreeTokens": "twonly-Codes für \"Free\"-Benutzer",
|
||||
"planNotAllowed": "In deinem aktuellen Plan kannst du keine Mediendateien versenden. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.",
|
||||
"planLimitReached": "Du hast dein Planlimit für heute erreicht. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden."
|
||||
"planLimitReached": "Du hast dein Planlimit für heute erreicht. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.",
|
||||
"galleryDelete": "Datei löschen",
|
||||
"galleryDetails": "Details anzeigen"
|
||||
}
|
||||
|
|
@ -390,5 +390,7 @@
|
|||
"additionalUsersPlusTokens": "twonly-Codes für \"Plus\" user",
|
||||
"additionalUsersFreeTokens": "twonly-Codes für \"Free\" user",
|
||||
"planLimitReached": "You have reached your plan limit for today. Upgrade your plan now to send the media file.",
|
||||
"planNotAllowed": "You cannot send media files with your current tariff. Upgrade your plan now to send the media file."
|
||||
"planNotAllowed": "You cannot send media files with your current tariff. Upgrade your plan now to send the media file.",
|
||||
"galleryDelete": "Delete file",
|
||||
"galleryDetails": "Show details"
|
||||
}
|
||||
|
|
@ -1408,6 +1408,18 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'You cannot send media files with your current tariff. Upgrade your plan now to send the media file.'**
|
||||
String get planNotAllowed;
|
||||
|
||||
/// No description provided for @galleryDelete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete file'**
|
||||
String get galleryDelete;
|
||||
|
||||
/// No description provided for @galleryDetails.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show details'**
|
||||
String get galleryDetails;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
|
|
|||
|
|
@ -682,4 +682,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get planNotAllowed => 'In deinem aktuellen Plan kannst du keine Mediendateien versenden. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.';
|
||||
|
||||
@override
|
||||
String get galleryDelete => 'Datei löschen';
|
||||
|
||||
@override
|
||||
String get galleryDetails => 'Details anzeigen';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -682,4 +682,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get planNotAllowed => 'You cannot send media files with your current tariff. Upgrade your plan now to send the media file.';
|
||||
|
||||
@override
|
||||
String get galleryDelete => 'Delete file';
|
||||
|
||||
@override
|
||||
String get galleryDetails => 'Show details';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import 'package:twonly/src/views/chats/chat_messages_components/chat_message_ent
|
|||
import 'package:twonly/src/views/chats/chat_messages_components/sliding_response.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_main_view.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_item.dart';
|
||||
|
||||
class ChatListEntry extends StatefulWidget {
|
||||
const ChatListEntry(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import 'package:twonly/src/providers/api/api.dart';
|
|||
import 'package:twonly/src/providers/api/media_received.dart' as received;
|
||||
import 'package:twonly/src/services/notification_service.dart';
|
||||
import 'package:twonly/src/views/chats/media_viewer_view.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_main_view.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_item.dart';
|
||||
|
||||
class ChatMediaEntry extends StatelessWidget {
|
||||
const ChatMediaEntry({
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import 'package:twonly/src/views/components/message_send_state_icon.dart';
|
|||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_main_view.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_item.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_photo_view.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class InChatMediaViewer extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import 'package:twonly/src/services/notification_service.dart';
|
|||
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/contact/contact_view.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_main_view.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_item.dart';
|
||||
|
||||
Color getMessageColor(Message message) {
|
||||
return (message.messageOtherId == null)
|
||||
|
|
|
|||
71
lib/src/views/gallery/gallery_item.dart
Normal file
71
lib/src/views/gallery/gallery_item.dart
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/providers/api/media_send.dart' as send;
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
|
||||
class GalleryItem {
|
||||
GalleryItem({
|
||||
required this.id,
|
||||
required this.messages,
|
||||
required this.date,
|
||||
required this.mirrorVideo,
|
||||
this.imagePath,
|
||||
this.videoPath,
|
||||
});
|
||||
final String id;
|
||||
final bool mirrorVideo;
|
||||
final List<Message> messages;
|
||||
final DateTime date;
|
||||
final File? imagePath;
|
||||
final File? videoPath;
|
||||
|
||||
static Future<Map<int, GalleryItem>> convertFromMessages(
|
||||
List<Message> messages) async {
|
||||
Map<int, GalleryItem> items = {};
|
||||
for (final message in messages) {
|
||||
bool isSend = message.messageOtherId == null;
|
||||
int id = message.mediaUploadId ?? message.messageId;
|
||||
final basePath = await send.getMediaFilePath(
|
||||
isSend ? message.mediaUploadId! : message.messageId,
|
||||
isSend ? "send" : "received",
|
||||
);
|
||||
File? imagePath;
|
||||
File? videoPath;
|
||||
if (await File("$basePath.mp4").exists()) {
|
||||
videoPath = File("$basePath.mp4");
|
||||
} else if (await File("$basePath.png").exists()) {
|
||||
imagePath = File("$basePath.png");
|
||||
} else {
|
||||
if (message.mediaStored) {
|
||||
/// media file was deleted, ... remove the file
|
||||
twonlyDatabase.messagesDao.updateMessageByMessageId(
|
||||
message.messageId, MessagesCompanion(mediaStored: Value(false)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
bool mirrorVideo = false;
|
||||
if (videoPath != null) {
|
||||
MediaMessageContent content =
|
||||
MediaMessageContent.fromJson(jsonDecode(message.contentJson!));
|
||||
mirrorVideo = content.mirrorVideo;
|
||||
}
|
||||
|
||||
items
|
||||
.putIfAbsent(
|
||||
id,
|
||||
() => GalleryItem(
|
||||
id: id.toString(),
|
||||
messages: [],
|
||||
date: message.sendAt,
|
||||
mirrorVideo: mirrorVideo,
|
||||
imagePath: imagePath,
|
||||
videoPath: videoPath))
|
||||
.messages
|
||||
.add(message);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
80
lib/src/views/gallery/gallery_item_thumbnail.dart
Normal file
80
lib/src/views/gallery/gallery_item_thumbnail.dart
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_item.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class GalleryItemThumbnail extends StatefulWidget {
|
||||
const GalleryItemThumbnail({
|
||||
super.key,
|
||||
required this.galleryItem,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final GalleryItem galleryItem;
|
||||
final GestureTapCallback onTap;
|
||||
|
||||
@override
|
||||
State<GalleryItemThumbnail> createState() => _GalleryItemThumbnailState();
|
||||
}
|
||||
|
||||
class _GalleryItemThumbnailState extends State<GalleryItemThumbnail> {
|
||||
VideoPlayerController? _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.galleryItem.videoPath != null) {
|
||||
_controller = VideoPlayerController.file(widget.galleryItem.videoPath!)
|
||||
..initialize().then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String formatDuration(Duration duration) {
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
|
||||
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
|
||||
return "$twoDigitMinutes:$twoDigitSeconds";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: Hero(
|
||||
tag: widget.galleryItem.id,
|
||||
child: (widget.galleryItem.imagePath != null)
|
||||
? Image.file(widget.galleryItem.imagePath!)
|
||||
: Stack(
|
||||
children: [
|
||||
if (_controller != null && _controller!.value.isInitialized)
|
||||
Positioned.fill(
|
||||
child: AspectRatio(
|
||||
aspectRatio: _controller!.value.aspectRatio,
|
||||
child: VideoPlayer(_controller!),
|
||||
)),
|
||||
if (_controller != null && _controller!.value.isInitialized)
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
right: 5,
|
||||
child: Text(
|
||||
_controller!.value.isInitialized
|
||||
? formatDuration(_controller!.value.duration)
|
||||
: '...',
|
||||
style: TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +1,13 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/providers/api/media_send.dart' as send;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||
import 'package:twonly/src/views/components/video_player_wrapper.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class GalleryItem {
|
||||
GalleryItem({
|
||||
required this.id,
|
||||
required this.messages,
|
||||
required this.date,
|
||||
required this.mirrorVideo,
|
||||
this.imagePath,
|
||||
this.videoPath,
|
||||
});
|
||||
final String id;
|
||||
final bool mirrorVideo;
|
||||
final List<Message> messages;
|
||||
final DateTime date;
|
||||
final File? imagePath;
|
||||
final File? videoPath;
|
||||
|
||||
static Future<Map<int, GalleryItem>> convertFromMessages(
|
||||
List<Message> messages) async {
|
||||
Map<int, GalleryItem> items = {};
|
||||
for (final message in messages) {
|
||||
bool isSend = message.messageOtherId == null;
|
||||
int id = message.mediaUploadId ?? message.messageId;
|
||||
final basePath = await send.getMediaFilePath(
|
||||
isSend ? message.mediaUploadId! : message.messageId,
|
||||
isSend ? "send" : "received",
|
||||
);
|
||||
File? imagePath;
|
||||
File? videoPath;
|
||||
if (await File("$basePath.mp4").exists()) {
|
||||
videoPath = File("$basePath.mp4");
|
||||
} else if (await File("$basePath.png").exists()) {
|
||||
imagePath = File("$basePath.png");
|
||||
} else {
|
||||
if (message.mediaStored) {
|
||||
/// media file was deleted, ... remove the file
|
||||
twonlyDatabase.messagesDao.updateMessageByMessageId(
|
||||
message.messageId, MessagesCompanion(mediaStored: Value(false)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
bool mirrorVideo = false;
|
||||
if (videoPath != null) {
|
||||
MediaMessageContent content =
|
||||
MediaMessageContent.fromJson(jsonDecode(message.contentJson!));
|
||||
mirrorVideo = content.mirrorVideo;
|
||||
}
|
||||
|
||||
items
|
||||
.putIfAbsent(
|
||||
id,
|
||||
() => GalleryItem(
|
||||
id: id.toString(),
|
||||
messages: [],
|
||||
date: message.sendAt,
|
||||
mirrorVideo: mirrorVideo,
|
||||
imagePath: imagePath,
|
||||
videoPath: videoPath))
|
||||
.messages
|
||||
.add(message);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
class GalleryItemGrid {
|
||||
GalleryItemGrid({
|
||||
this.galleryItemIndex,
|
||||
this.month,
|
||||
this.hide,
|
||||
});
|
||||
final int? galleryItemIndex;
|
||||
final String? month;
|
||||
final bool? hide;
|
||||
}
|
||||
|
||||
class GalleryItemThumbnail extends StatefulWidget {
|
||||
const GalleryItemThumbnail({
|
||||
super.key,
|
||||
required this.galleryItem,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final GalleryItem galleryItem;
|
||||
final GestureTapCallback onTap;
|
||||
|
||||
@override
|
||||
State<GalleryItemThumbnail> createState() => _GalleryItemThumbnailState();
|
||||
}
|
||||
|
||||
class _GalleryItemThumbnailState extends State<GalleryItemThumbnail> {
|
||||
VideoPlayerController? _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.galleryItem.videoPath != null) {
|
||||
_controller = VideoPlayerController.file(widget.galleryItem.videoPath!)
|
||||
..initialize().then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String formatDuration(Duration duration) {
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
|
||||
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
|
||||
return "$twoDigitMinutes:$twoDigitSeconds";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: Hero(
|
||||
tag: widget.galleryItem.id,
|
||||
child: (widget.galleryItem.imagePath != null)
|
||||
? Image.file(widget.galleryItem.imagePath!)
|
||||
: Stack(
|
||||
children: [
|
||||
if (_controller != null && _controller!.value.isInitialized)
|
||||
Positioned.fill(
|
||||
child: AspectRatio(
|
||||
aspectRatio: _controller!.value.aspectRatio,
|
||||
child: VideoPlayer(_controller!),
|
||||
)),
|
||||
if (_controller != null && _controller!.value.isInitialized)
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
right: 5,
|
||||
child: Text(
|
||||
_controller!.value.isInitialized
|
||||
? formatDuration(_controller!.value.duration)
|
||||
: '...',
|
||||
style: TextStyle(
|
||||
fontSize: 15, fontWeight: FontWeight.bold),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
import 'package:twonly/src/views/gallery/gallery_item.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_item_thumbnail.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_photo_view.dart';
|
||||
|
||||
class GalleryMainView extends StatefulWidget {
|
||||
const GalleryMainView({super.key});
|
||||
|
|
@ -181,6 +22,7 @@ class GalleryMainViewState extends State<GalleryMainView> {
|
|||
Map<String, List<int>> orderedByMonth = {};
|
||||
List<String> months = [];
|
||||
bool mounted = true;
|
||||
StreamSubscription<List<Message>>? messageSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -191,6 +33,7 @@ class GalleryMainViewState extends State<GalleryMainView> {
|
|||
@override
|
||||
void dispose() {
|
||||
mounted = false;
|
||||
messageSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -230,12 +73,12 @@ class GalleryMainViewState extends State<GalleryMainView> {
|
|||
}
|
||||
|
||||
Future initAsync() async {
|
||||
List<Message> storedMediaFiles =
|
||||
await twonlyDatabase.messagesDao.getAllStoredMediaFiles();
|
||||
|
||||
Map<int, GalleryItem> items =
|
||||
await GalleryItem.convertFromMessages(storedMediaFiles);
|
||||
messageSub?.cancel();
|
||||
Stream<List<Message>> msgStream =
|
||||
twonlyDatabase.messagesDao.getAllStoredMediaFiles();
|
||||
|
||||
messageSub = msgStream.listen((msgs) async {
|
||||
Map<int, GalleryItem> items = await GalleryItem.convertFromMessages(msgs);
|
||||
// Group items by month
|
||||
orderedByMonth = {};
|
||||
months = [];
|
||||
|
|
@ -254,6 +97,7 @@ class GalleryMainViewState extends State<GalleryMainView> {
|
|||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -296,144 +140,17 @@ class GalleryMainViewState extends State<GalleryMainView> {
|
|||
);
|
||||
}
|
||||
|
||||
void open(BuildContext context, final int index) {
|
||||
Navigator.push(
|
||||
void open(BuildContext context, final int index) async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => GalleryPhotoViewWrapper(
|
||||
galleryItems: galleryItems,
|
||||
// backgroundDecoration: const BoxDecoration(
|
||||
// color: Colors.black,
|
||||
// ),
|
||||
initialIndex: index,
|
||||
scrollDirection: verticalGallery ? Axis.vertical : Axis.horizontal,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GalleryPhotoViewWrapper extends StatefulWidget {
|
||||
GalleryPhotoViewWrapper({
|
||||
super.key,
|
||||
this.loadingBuilder,
|
||||
this.backgroundDecoration,
|
||||
this.minScale,
|
||||
this.maxScale,
|
||||
this.initialIndex = 0,
|
||||
required this.galleryItems,
|
||||
this.scrollDirection = Axis.horizontal,
|
||||
}) : pageController = PageController(initialPage: initialIndex);
|
||||
|
||||
final LoadingBuilder? loadingBuilder;
|
||||
final BoxDecoration? backgroundDecoration;
|
||||
final dynamic minScale;
|
||||
final dynamic maxScale;
|
||||
final int initialIndex;
|
||||
final PageController pageController;
|
||||
final List<GalleryItem> galleryItems;
|
||||
final Axis scrollDirection;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _GalleryPhotoViewWrapperState();
|
||||
}
|
||||
}
|
||||
|
||||
class _GalleryPhotoViewWrapperState extends State<GalleryPhotoViewWrapper> {
|
||||
late int currentIndex = widget.initialIndex;
|
||||
|
||||
void onPageChanged(int index) {
|
||||
setState(() {
|
||||
currentIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: widget.backgroundDecoration,
|
||||
constraints: BoxConstraints.expand(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: <Widget>[
|
||||
MediaViewSizing(
|
||||
bottomNavigation: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||
onPressed: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ShareImageEditorView(
|
||||
videoFilePath:
|
||||
widget.galleryItems[currentIndex].videoPath,
|
||||
imageBytes: widget
|
||||
.galleryItems[currentIndex].imagePath
|
||||
?.readAsBytes(),
|
||||
mirrorVideo: false,
|
||||
useHighQuality: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
)),
|
||||
label: Text(
|
||||
context.lang.shareImagedEditorSendImage,
|
||||
style: TextStyle(fontSize: 17),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PhotoViewGallery.builder(
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
builder: _buildItem,
|
||||
itemCount: widget.galleryItems.length,
|
||||
loadingBuilder: widget.loadingBuilder,
|
||||
backgroundDecoration: widget.backgroundDecoration,
|
||||
pageController: widget.pageController,
|
||||
onPageChanged: onPageChanged,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
|
||||
final GalleryItem item = widget.galleryItems[index];
|
||||
return item.videoPath != null
|
||||
? PhotoViewGalleryPageOptions.customChild(
|
||||
child: VideoPlayerWrapper(
|
||||
videoPath: item.videoPath!,
|
||||
mirrorVideo: item.mirrorVideo,
|
||||
),
|
||||
// childSize: const Size(300, 300),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 4.1,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: item.id),
|
||||
)
|
||||
: PhotoViewGalleryPageOptions(
|
||||
imageProvider: FileImage(item.imagePath!),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 4.1,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: item.id),
|
||||
);
|
||||
initAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
190
lib/src/views/gallery/gallery_photo_view.dart
Normal file
190
lib/src/views/gallery/gallery_photo_view.dart
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/providers/api/media_received.dart' as received;
|
||||
import 'package:twonly/src/providers/api/media_send.dart' as send;
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||
import 'package:twonly/src/views/components/video_player_wrapper.dart';
|
||||
import 'package:twonly/src/views/gallery/gallery_item.dart';
|
||||
|
||||
class GalleryPhotoViewWrapper extends StatefulWidget {
|
||||
GalleryPhotoViewWrapper({
|
||||
super.key,
|
||||
this.loadingBuilder,
|
||||
this.backgroundDecoration,
|
||||
this.minScale,
|
||||
this.maxScale,
|
||||
this.initialIndex = 0,
|
||||
required this.galleryItems,
|
||||
this.scrollDirection = Axis.horizontal,
|
||||
}) : pageController = PageController(initialPage: initialIndex);
|
||||
|
||||
final LoadingBuilder? loadingBuilder;
|
||||
final BoxDecoration? backgroundDecoration;
|
||||
final dynamic minScale;
|
||||
final dynamic maxScale;
|
||||
final int initialIndex;
|
||||
final PageController pageController;
|
||||
final List<GalleryItem> galleryItems;
|
||||
final Axis scrollDirection;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _GalleryPhotoViewWrapperState();
|
||||
}
|
||||
}
|
||||
|
||||
class _GalleryPhotoViewWrapperState extends State<GalleryPhotoViewWrapper> {
|
||||
late int currentIndex = widget.initialIndex;
|
||||
|
||||
void onPageChanged(int index) {
|
||||
setState(() {
|
||||
currentIndex = index;
|
||||
});
|
||||
}
|
||||
|
||||
Future deleteFile() async {
|
||||
List<Message> messages = widget.galleryItems[currentIndex].messages;
|
||||
bool confirmed = await showAlertDialog(
|
||||
context, "Are you sure?", "The image will be irrevocably deleted.");
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
widget.galleryItems[currentIndex].imagePath?.deleteSync();
|
||||
widget.galleryItems[currentIndex].videoPath?.deleteSync();
|
||||
for (final message in messages) {
|
||||
await twonlyDatabase.messagesDao.updateMessageByMessageId(
|
||||
message.messageId,
|
||||
MessagesCompanion(mediaStored: Value(false)),
|
||||
);
|
||||
}
|
||||
|
||||
widget.galleryItems.removeAt(currentIndex);
|
||||
setState(() {});
|
||||
await send.purgeSendMediaFiles();
|
||||
await received.purgeReceivedMediaFiles();
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: widget.backgroundDecoration,
|
||||
constraints: BoxConstraints.expand(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: <Widget>[
|
||||
MediaViewSizing(
|
||||
bottomNavigation: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FilledButton.icon(
|
||||
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||
onPressed: () async {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ShareImageEditorView(
|
||||
videoFilePath:
|
||||
widget.galleryItems[currentIndex].videoPath,
|
||||
imageBytes: widget
|
||||
.galleryItems[currentIndex].imagePath
|
||||
?.readAsBytes(),
|
||||
mirrorVideo: false,
|
||||
useHighQuality: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||
EdgeInsets.symmetric(vertical: 10, horizontal: 30),
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.all<Color>(
|
||||
Theme.of(context).colorScheme.primary,
|
||||
)),
|
||||
label: Text(
|
||||
context.lang.shareImagedEditorSendImage,
|
||||
style: TextStyle(fontSize: 17),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
PhotoViewGallery.builder(
|
||||
scrollPhysics: const BouncingScrollPhysics(),
|
||||
builder: _buildItem,
|
||||
itemCount: widget.galleryItems.length,
|
||||
loadingBuilder: widget.loadingBuilder,
|
||||
backgroundDecoration: widget.backgroundDecoration,
|
||||
pageController: widget.pageController,
|
||||
onPageChanged: onPageChanged,
|
||||
scrollDirection: widget.scrollDirection,
|
||||
),
|
||||
Positioned(
|
||||
right: 5,
|
||||
child: PopupMenuButton<String>(
|
||||
onSelected: (String result) {
|
||||
if (result == "delete") {
|
||||
deleteFile();
|
||||
}
|
||||
},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
value: 'delete',
|
||||
child: Text(context.lang.galleryDelete),
|
||||
),
|
||||
// PopupMenuItem<String>(
|
||||
// value: 'details',
|
||||
// child: Text(context.lang.galleryDetails),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
|
||||
final GalleryItem item = widget.galleryItems[index];
|
||||
return item.videoPath != null
|
||||
? PhotoViewGalleryPageOptions.customChild(
|
||||
child: VideoPlayerWrapper(
|
||||
videoPath: item.videoPath!,
|
||||
mirrorVideo: item.mirrorVideo,
|
||||
),
|
||||
// childSize: const Size(300, 300),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 4.1,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: item.id),
|
||||
)
|
||||
: PhotoViewGalleryPageOptions(
|
||||
imageProvider: FileImage(item.imagePath!),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 4.1,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: item.id),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue