✨ Attachment video preview
This commit is contained in:
@@ -324,6 +324,9 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- video_thumbnail (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- libwebp
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WebRTC-SDK (137.7151.04)
|
- WebRTC-SDK (137.7151.04)
|
||||||
@@ -379,6 +382,7 @@ DEPENDENCIES:
|
|||||||
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
|
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
|
||||||
- syncfusion_flutter_pdfviewer (from `.symlinks/plugins/syncfusion_flutter_pdfviewer/ios`)
|
- syncfusion_flutter_pdfviewer (from `.symlinks/plugins/syncfusion_flutter_pdfviewer/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
@@ -508,6 +512,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/syncfusion_flutter_pdfviewer/ios"
|
:path: ".symlinks/plugins/syncfusion_flutter_pdfviewer/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
video_thumbnail:
|
||||||
|
:path: ".symlinks/plugins/video_thumbnail/ios"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
|
|
||||||
@@ -587,6 +593,7 @@ SPEC CHECKSUMS:
|
|||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14
|
syncfusion_flutter_pdfviewer: 90dc48305d2e33d4aa20681d1e98ddeda891bc14
|
||||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||||
|
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cross_file/cross_file.dart';
|
import 'package:cross_file/cross_file.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:video_thumbnail/video_thumbnail.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -275,9 +277,184 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
var ratio = item.isOnCloud
|
var ratio = item.isOnCloud
|
||||||
? (item.data.fileMeta?['ratio'] is num
|
? (item.data.fileMeta?['ratio'] is num
|
||||||
? item.data.fileMeta!['ratio'].toDouble()
|
? item.data.fileMeta!['ratio'].toDouble()
|
||||||
: 1.0)
|
: null)
|
||||||
: 1.0;
|
: null;
|
||||||
if (ratio == 0) ratio = 1.0;
|
|
||||||
|
final innerContentWidget = Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
HookBuilder(
|
||||||
|
key: ValueKey(item.hashCode),
|
||||||
|
builder: (context) {
|
||||||
|
final fallbackIcon = switch (item.type) {
|
||||||
|
UniversalFileType.video => Symbols.video_file,
|
||||||
|
UniversalFileType.audio => Symbols.audio_file,
|
||||||
|
UniversalFileType.image => Symbols.image,
|
||||||
|
_ => Symbols.insert_drive_file,
|
||||||
|
};
|
||||||
|
|
||||||
|
final mimeType = FileUploader.getMimeType(item);
|
||||||
|
|
||||||
|
if (item.isOnCloud) {
|
||||||
|
return CloudFileWidget(item: item.data);
|
||||||
|
} else if (item.data is XFile) {
|
||||||
|
final file = item.data as XFile;
|
||||||
|
if (file.path.isEmpty) {
|
||||||
|
return FutureBuilder<Uint8List>(
|
||||||
|
future: file.readAsBytes(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
return Image.memory(snapshot.data!);
|
||||||
|
}
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case UniversalFileType.image:
|
||||||
|
return kIsWeb
|
||||||
|
? Image.network(file.path)
|
||||||
|
: Image.file(File(file.path));
|
||||||
|
case UniversalFileType.video:
|
||||||
|
if (!kIsWeb) {
|
||||||
|
final thumbnailFuture = useMemoized(
|
||||||
|
() => VideoThumbnail.thumbnailData(
|
||||||
|
video: file.path,
|
||||||
|
imageFormat: ImageFormat.JPEG,
|
||||||
|
maxWidth: 320,
|
||||||
|
quality: 50,
|
||||||
|
),
|
||||||
|
[file.path],
|
||||||
|
);
|
||||||
|
return FutureBuilder<Uint8List?>(
|
||||||
|
future: thumbnailFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData && snapshot.data != null) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Image.memory(snapshot.data!),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Symbols.play_arrow,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(fallbackIcon),
|
||||||
|
const Gap(6),
|
||||||
|
Text(
|
||||||
|
_getDisplayName(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
Text(mimeType, style: TextStyle(fontSize: 10)),
|
||||||
|
const Gap(1),
|
||||||
|
FutureBuilder(
|
||||||
|
future: file.length(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
final size = snapshot.data as int;
|
||||||
|
return Text(formatFileSize(size)).fontSize(11);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(vertical: 32);
|
||||||
|
} else if (item is List<int> || item is Uint8List) {
|
||||||
|
switch (item.type) {
|
||||||
|
case UniversalFileType.image:
|
||||||
|
return Image.memory(item.data);
|
||||||
|
default:
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(fallbackIcon),
|
||||||
|
const Gap(6),
|
||||||
|
Text(mimeType, style: TextStyle(fontSize: 10)),
|
||||||
|
const Gap(1),
|
||||||
|
Text(formatFileSize(item.data.length)).fontSize(11),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Placeholder();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (isUploading && progress != null && (progress ?? 0) > 0)
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${(progress! * 100).toStringAsFixed(2)}%',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
Gap(6),
|
||||||
|
Center(
|
||||||
|
child: TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween<double>(begin: 0.0, end: progress),
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
builder: (context, value, child) =>
|
||||||
|
LinearProgressIndicator(value: value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isUploading && (progress == null || progress == 0))
|
||||||
|
Positioned.fill(
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'processing'.tr(),
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
Gap(6),
|
||||||
|
Center(child: LinearProgressIndicator(value: null)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
final contentWidget = ClipRRect(
|
final contentWidget = ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
@@ -285,149 +462,13 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
if (ratio != null)
|
||||||
aspectRatio: ratio,
|
AspectRatio(
|
||||||
child: Stack(
|
aspectRatio: ratio,
|
||||||
fit: StackFit.expand,
|
child: innerContentWidget,
|
||||||
children: [
|
).center()
|
||||||
Builder(
|
else
|
||||||
key: ValueKey(item.hashCode),
|
IntrinsicHeight(child: innerContentWidget),
|
||||||
builder: (context) {
|
|
||||||
final fallbackIcon = switch (item.type) {
|
|
||||||
UniversalFileType.video => Symbols.video_file,
|
|
||||||
UniversalFileType.audio => Symbols.audio_file,
|
|
||||||
UniversalFileType.image => Symbols.image,
|
|
||||||
_ => Symbols.insert_drive_file,
|
|
||||||
};
|
|
||||||
|
|
||||||
final mimeType = FileUploader.getMimeType(item);
|
|
||||||
|
|
||||||
if (item.isOnCloud) {
|
|
||||||
return CloudFileWidget(item: item.data);
|
|
||||||
} else if (item.data is XFile) {
|
|
||||||
final file = item.data as XFile;
|
|
||||||
if (file.path.isEmpty) {
|
|
||||||
return FutureBuilder<Uint8List>(
|
|
||||||
future: file.readAsBytes(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
return Image.memory(snapshot.data!);
|
|
||||||
}
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (item.type) {
|
|
||||||
case UniversalFileType.image:
|
|
||||||
return kIsWeb
|
|
||||||
? Image.network(file.path)
|
|
||||||
: Image.file(File(file.path));
|
|
||||||
default:
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(fallbackIcon),
|
|
||||||
const Gap(6),
|
|
||||||
Text(
|
|
||||||
_getDisplayName(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
Text(mimeType, style: TextStyle(fontSize: 10)),
|
|
||||||
const Gap(1),
|
|
||||||
FutureBuilder(
|
|
||||||
future: file.length(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasData) {
|
|
||||||
final size = snapshot.data as int;
|
|
||||||
return Text(
|
|
||||||
formatFileSize(size),
|
|
||||||
).fontSize(11);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (item is List<int> || item is Uint8List) {
|
|
||||||
switch (item.type) {
|
|
||||||
case UniversalFileType.image:
|
|
||||||
return Image.memory(item.data);
|
|
||||||
default:
|
|
||||||
return Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(fallbackIcon),
|
|
||||||
const Gap(6),
|
|
||||||
Text(mimeType, style: TextStyle(fontSize: 10)),
|
|
||||||
const Gap(1),
|
|
||||||
Text(
|
|
||||||
formatFileSize(item.data.length),
|
|
||||||
).fontSize(11),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Placeholder();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (isUploading && progress != null && (progress ?? 0) > 0)
|
|
||||||
Positioned.fill(
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 40,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${(progress! * 100).toStringAsFixed(2)}%',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
Gap(6),
|
|
||||||
Center(
|
|
||||||
child: TweenAnimationBuilder<double>(
|
|
||||||
tween: Tween<double>(begin: 0.0, end: progress),
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
builder: (context, value, child) => LinearProgressIndicator(value: value),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isUploading && (progress == null || progress == 0))
|
|
||||||
Positioned.fill(
|
|
||||||
child: Container(
|
|
||||||
color: Colors.black.withOpacity(0.3),
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 40,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'processing'.tr(),
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
Gap(6),
|
|
||||||
Center(child: LinearProgressIndicator(value: null)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).center(),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -605,4 +646,4 @@ class AttachmentPreview extends HookConsumerWidget {
|
|||||||
child: contentWidget,
|
child: contentWidget,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3195,6 +3195,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
video_thumbnail:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: video_thumbnail
|
||||||
|
sha256: "181a0c205b353918954a881f53a3441476b9e301641688a581e0c13f00dc588b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.6"
|
||||||
visibility_detector:
|
visibility_detector:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ dependencies:
|
|||||||
in_app_review: ^2.0.11
|
in_app_review: ^2.0.11
|
||||||
snow_fall_animation: ^0.0.1+3
|
snow_fall_animation: ^0.0.1+3
|
||||||
flutter_app_intents: ^0.7.0
|
flutter_app_intents: ^0.7.0
|
||||||
|
video_thumbnail: ^0.5.6
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -274,4 +275,4 @@ msix_config:
|
|||||||
logo_path: .\assets\icons\icon.png
|
logo_path: .\assets\icons\icon.png
|
||||||
protocol_activation: solian, https
|
protocol_activation: solian, https
|
||||||
app_uri_handler_hosts: solian.app
|
app_uri_handler_hosts: solian.app
|
||||||
capabilities: internetClientServer, location, microphone, webcam
|
capabilities: internetClientServer, location, microphone, webcam
|
||||||
Reference in New Issue
Block a user