mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
integrating ffmpeg
This commit is contained in:
parent
bd4c30ed4d
commit
35152cce23
23 changed files with 351 additions and 239 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -2,16 +2,18 @@
|
|||
|
||||
## 0.0.62
|
||||
|
||||
- Support for Groups
|
||||
- Editing of text messages
|
||||
- Deletion of messages
|
||||
- Various UI improvements like a new context-menu
|
||||
- Client-to-client (C2C) protocol converted to ProtoBuf
|
||||
- Use of UUIDs in the database
|
||||
- Completely new database schema
|
||||
- Improved reliability of C2C messages
|
||||
- Improved video handling
|
||||
- Various bug fixes
|
||||
- Support for groups
|
||||
- Edit & Delete messages
|
||||
- Switched to FFmpeg for improved video compression
|
||||
- Video max. length increased to 60 seconds
|
||||
- Removing audio after recording is possible
|
||||
- Edited image is now embedded into the video
|
||||
- New context menu and other UI enhancements
|
||||
- Client-to-client protocol migrated to Protocol Buffers (Protobuf)
|
||||
- Database identifiers converted to UUIDs
|
||||
- Completely redesigned database schema
|
||||
- Improved reliability of client-to-client messaging
|
||||
- Multiple bug fixes
|
||||
|
||||
## 0.0.61
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ PODS:
|
|||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- ffmpeg_kit_flutter_new_min_gpl (7.1.1):
|
||||
- ffmpeg_kit_flutter_new_min_gpl/min-gpl (= 7.1.1)
|
||||
- Flutter
|
||||
- ffmpeg_kit_flutter_new_min_gpl/min-gpl (7.1.1):
|
||||
- Flutter
|
||||
- Firebase (12.4.0):
|
||||
- Firebase/Core (= 12.4.0)
|
||||
- Firebase/Core (12.4.0):
|
||||
|
|
@ -233,8 +238,6 @@ PODS:
|
|||
- SwiftProtobuf (1.32.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_compress (0.3.0):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
|
|
@ -248,6 +251,7 @@ DEPENDENCIES:
|
|||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- cryptography_flutter_plus (from `.symlinks/plugins/cryptography_flutter_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- ffmpeg_kit_flutter_new_min_gpl (from `.symlinks/plugins/ffmpeg_kit_flutter_new_min_gpl/ios`)
|
||||
- Firebase
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
|
|
@ -275,7 +279,6 @@ DEPENDENCIES:
|
|||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- SwiftProtobuf
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
|
||||
|
||||
|
|
@ -312,6 +315,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/cryptography_flutter_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
ffmpeg_kit_flutter_new_min_gpl:
|
||||
:path: ".symlinks/plugins/ffmpeg_kit_flutter_new_min_gpl/ios"
|
||||
firebase_core:
|
||||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
firebase_messaging:
|
||||
|
|
@ -354,8 +359,6 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_compress:
|
||||
:path: ".symlinks/plugins/video_compress/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||
video_thumbnail:
|
||||
|
|
@ -367,6 +370,7 @@ SPEC CHECKSUMS:
|
|||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
ffmpeg_kit_flutter_new_min_gpl: 79212bc20869b4e12ec06705724c26b016e9d58e
|
||||
Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e
|
||||
firebase_core: 744984dbbed8b3036abf34f0b98d80f130a7e464
|
||||
firebase_messaging: 82c70650c426a0a14873e1acdb9ec2b443c4e8b4
|
||||
|
|
@ -407,7 +411,6 @@ SPEC CHECKSUMS:
|
|||
sqlite3_flutter_libs: 83f8e9f5b6554077f1d93119fe20ebaa5f3a9ef1
|
||||
SwiftProtobuf: 81e341191afbddd64aa031bd12862dccfab2f639
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
video_compress: f2133a07762889d67f0711ac831faa26f956980e
|
||||
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
|
||||
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class MediaFiles extends Table {
|
|||
text().map(IntListTypeConverter()).nullable()();
|
||||
|
||||
IntColumn get displayLimitInMilliseconds => integer().nullable()();
|
||||
BoolColumn get removeAudio => boolean().nullable()();
|
||||
|
||||
BlobColumn get downloadToken => blob().nullable()();
|
||||
BlobColumn get encryptionKey => blob().nullable()();
|
||||
|
|
|
|||
|
|
@ -1561,6 +1561,15 @@ class $MediaFilesTable extends MediaFiles
|
|||
late final GeneratedColumn<int> displayLimitInMilliseconds =
|
||||
GeneratedColumn<int>('display_limit_in_milliseconds', aliasedName, true,
|
||||
type: DriftSqlType.int, requiredDuringInsert: false);
|
||||
static const VerificationMeta _removeAudioMeta =
|
||||
const VerificationMeta('removeAudio');
|
||||
@override
|
||||
late final GeneratedColumn<bool> removeAudio = GeneratedColumn<bool>(
|
||||
'remove_audio', aliasedName, true,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("remove_audio" IN (0, 1))'));
|
||||
static const VerificationMeta _downloadTokenMeta =
|
||||
const VerificationMeta('downloadToken');
|
||||
@override
|
||||
|
|
@ -1604,6 +1613,7 @@ class $MediaFilesTable extends MediaFiles
|
|||
stored,
|
||||
reuploadRequestedBy,
|
||||
displayLimitInMilliseconds,
|
||||
removeAudio,
|
||||
downloadToken,
|
||||
encryptionKey,
|
||||
encryptionMac,
|
||||
|
|
@ -1649,6 +1659,12 @@ class $MediaFilesTable extends MediaFiles
|
|||
data['display_limit_in_milliseconds']!,
|
||||
_displayLimitInMillisecondsMeta));
|
||||
}
|
||||
if (data.containsKey('remove_audio')) {
|
||||
context.handle(
|
||||
_removeAudioMeta,
|
||||
removeAudio.isAcceptableOrUnknown(
|
||||
data['remove_audio']!, _removeAudioMeta));
|
||||
}
|
||||
if (data.containsKey('download_token')) {
|
||||
context.handle(
|
||||
_downloadTokenMeta,
|
||||
|
|
@ -1709,6 +1725,8 @@ class $MediaFilesTable extends MediaFiles
|
|||
displayLimitInMilliseconds: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}display_limit_in_milliseconds']),
|
||||
removeAudio: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.bool, data['${effectivePrefix}remove_audio']),
|
||||
downloadToken: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.blob, data['${effectivePrefix}download_token']),
|
||||
encryptionKey: attachedDatabase.typeMapping
|
||||
|
|
@ -1756,6 +1774,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
final bool stored;
|
||||
final List<int>? reuploadRequestedBy;
|
||||
final int? displayLimitInMilliseconds;
|
||||
final bool? removeAudio;
|
||||
final Uint8List? downloadToken;
|
||||
final Uint8List? encryptionKey;
|
||||
final Uint8List? encryptionMac;
|
||||
|
|
@ -1771,6 +1790,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
required this.stored,
|
||||
this.reuploadRequestedBy,
|
||||
this.displayLimitInMilliseconds,
|
||||
this.removeAudio,
|
||||
this.downloadToken,
|
||||
this.encryptionKey,
|
||||
this.encryptionMac,
|
||||
|
|
@ -1804,6 +1824,9 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
map['display_limit_in_milliseconds'] =
|
||||
Variable<int>(displayLimitInMilliseconds);
|
||||
}
|
||||
if (!nullToAbsent || removeAudio != null) {
|
||||
map['remove_audio'] = Variable<bool>(removeAudio);
|
||||
}
|
||||
if (!nullToAbsent || downloadToken != null) {
|
||||
map['download_token'] = Variable<Uint8List>(downloadToken);
|
||||
}
|
||||
|
|
@ -1840,6 +1863,9 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
displayLimitInMilliseconds == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(displayLimitInMilliseconds),
|
||||
removeAudio: removeAudio == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(removeAudio),
|
||||
downloadToken: downloadToken == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(downloadToken),
|
||||
|
|
@ -1875,6 +1901,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
serializer.fromJson<List<int>?>(json['reuploadRequestedBy']),
|
||||
displayLimitInMilliseconds:
|
||||
serializer.fromJson<int?>(json['displayLimitInMilliseconds']),
|
||||
removeAudio: serializer.fromJson<bool?>(json['removeAudio']),
|
||||
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
||||
encryptionKey: serializer.fromJson<Uint8List?>(json['encryptionKey']),
|
||||
encryptionMac: serializer.fromJson<Uint8List?>(json['encryptionMac']),
|
||||
|
|
@ -1899,6 +1926,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
'reuploadRequestedBy': serializer.toJson<List<int>?>(reuploadRequestedBy),
|
||||
'displayLimitInMilliseconds':
|
||||
serializer.toJson<int?>(displayLimitInMilliseconds),
|
||||
'removeAudio': serializer.toJson<bool?>(removeAudio),
|
||||
'downloadToken': serializer.toJson<Uint8List?>(downloadToken),
|
||||
'encryptionKey': serializer.toJson<Uint8List?>(encryptionKey),
|
||||
'encryptionMac': serializer.toJson<Uint8List?>(encryptionMac),
|
||||
|
|
@ -1917,6 +1945,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
bool? stored,
|
||||
Value<List<int>?> reuploadRequestedBy = const Value.absent(),
|
||||
Value<int?> displayLimitInMilliseconds = const Value.absent(),
|
||||
Value<bool?> removeAudio = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<Uint8List?> encryptionKey = const Value.absent(),
|
||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||
|
|
@ -1938,6 +1967,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
displayLimitInMilliseconds: displayLimitInMilliseconds.present
|
||||
? displayLimitInMilliseconds.value
|
||||
: this.displayLimitInMilliseconds,
|
||||
removeAudio: removeAudio.present ? removeAudio.value : this.removeAudio,
|
||||
downloadToken:
|
||||
downloadToken.present ? downloadToken.value : this.downloadToken,
|
||||
encryptionKey:
|
||||
|
|
@ -1971,6 +2001,8 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
displayLimitInMilliseconds: data.displayLimitInMilliseconds.present
|
||||
? data.displayLimitInMilliseconds.value
|
||||
: this.displayLimitInMilliseconds,
|
||||
removeAudio:
|
||||
data.removeAudio.present ? data.removeAudio.value : this.removeAudio,
|
||||
downloadToken: data.downloadToken.present
|
||||
? data.downloadToken.value
|
||||
: this.downloadToken,
|
||||
|
|
@ -1999,6 +2031,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
..write('stored: $stored, ')
|
||||
..write('reuploadRequestedBy: $reuploadRequestedBy, ')
|
||||
..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ')
|
||||
..write('removeAudio: $removeAudio, ')
|
||||
..write('downloadToken: $downloadToken, ')
|
||||
..write('encryptionKey: $encryptionKey, ')
|
||||
..write('encryptionMac: $encryptionMac, ')
|
||||
|
|
@ -2019,6 +2052,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
stored,
|
||||
reuploadRequestedBy,
|
||||
displayLimitInMilliseconds,
|
||||
removeAudio,
|
||||
$driftBlobEquality.hash(downloadToken),
|
||||
$driftBlobEquality.hash(encryptionKey),
|
||||
$driftBlobEquality.hash(encryptionMac),
|
||||
|
|
@ -2037,6 +2071,7 @@ class MediaFile extends DataClass implements Insertable<MediaFile> {
|
|||
other.stored == this.stored &&
|
||||
other.reuploadRequestedBy == this.reuploadRequestedBy &&
|
||||
other.displayLimitInMilliseconds == this.displayLimitInMilliseconds &&
|
||||
other.removeAudio == this.removeAudio &&
|
||||
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
||||
$driftBlobEquality.equals(other.encryptionKey, this.encryptionKey) &&
|
||||
$driftBlobEquality.equals(other.encryptionMac, this.encryptionMac) &&
|
||||
|
|
@ -2055,6 +2090,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
final Value<bool> stored;
|
||||
final Value<List<int>?> reuploadRequestedBy;
|
||||
final Value<int?> displayLimitInMilliseconds;
|
||||
final Value<bool?> removeAudio;
|
||||
final Value<Uint8List?> downloadToken;
|
||||
final Value<Uint8List?> encryptionKey;
|
||||
final Value<Uint8List?> encryptionMac;
|
||||
|
|
@ -2071,6 +2107,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
this.stored = const Value.absent(),
|
||||
this.reuploadRequestedBy = const Value.absent(),
|
||||
this.displayLimitInMilliseconds = const Value.absent(),
|
||||
this.removeAudio = const Value.absent(),
|
||||
this.downloadToken = const Value.absent(),
|
||||
this.encryptionKey = const Value.absent(),
|
||||
this.encryptionMac = const Value.absent(),
|
||||
|
|
@ -2088,6 +2125,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
this.stored = const Value.absent(),
|
||||
this.reuploadRequestedBy = const Value.absent(),
|
||||
this.displayLimitInMilliseconds = const Value.absent(),
|
||||
this.removeAudio = const Value.absent(),
|
||||
this.downloadToken = const Value.absent(),
|
||||
this.encryptionKey = const Value.absent(),
|
||||
this.encryptionMac = const Value.absent(),
|
||||
|
|
@ -2106,6 +2144,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
Expression<bool>? stored,
|
||||
Expression<String>? reuploadRequestedBy,
|
||||
Expression<int>? displayLimitInMilliseconds,
|
||||
Expression<bool>? removeAudio,
|
||||
Expression<Uint8List>? downloadToken,
|
||||
Expression<Uint8List>? encryptionKey,
|
||||
Expression<Uint8List>? encryptionMac,
|
||||
|
|
@ -2126,6 +2165,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
'reupload_requested_by': reuploadRequestedBy,
|
||||
if (displayLimitInMilliseconds != null)
|
||||
'display_limit_in_milliseconds': displayLimitInMilliseconds,
|
||||
if (removeAudio != null) 'remove_audio': removeAudio,
|
||||
if (downloadToken != null) 'download_token': downloadToken,
|
||||
if (encryptionKey != null) 'encryption_key': encryptionKey,
|
||||
if (encryptionMac != null) 'encryption_mac': encryptionMac,
|
||||
|
|
@ -2145,6 +2185,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
Value<bool>? stored,
|
||||
Value<List<int>?>? reuploadRequestedBy,
|
||||
Value<int?>? displayLimitInMilliseconds,
|
||||
Value<bool?>? removeAudio,
|
||||
Value<Uint8List?>? downloadToken,
|
||||
Value<Uint8List?>? encryptionKey,
|
||||
Value<Uint8List?>? encryptionMac,
|
||||
|
|
@ -2163,6 +2204,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
reuploadRequestedBy: reuploadRequestedBy ?? this.reuploadRequestedBy,
|
||||
displayLimitInMilliseconds:
|
||||
displayLimitInMilliseconds ?? this.displayLimitInMilliseconds,
|
||||
removeAudio: removeAudio ?? this.removeAudio,
|
||||
downloadToken: downloadToken ?? this.downloadToken,
|
||||
encryptionKey: encryptionKey ?? this.encryptionKey,
|
||||
encryptionMac: encryptionMac ?? this.encryptionMac,
|
||||
|
|
@ -2209,6 +2251,9 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
map['display_limit_in_milliseconds'] =
|
||||
Variable<int>(displayLimitInMilliseconds.value);
|
||||
}
|
||||
if (removeAudio.present) {
|
||||
map['remove_audio'] = Variable<bool>(removeAudio.value);
|
||||
}
|
||||
if (downloadToken.present) {
|
||||
map['download_token'] = Variable<Uint8List>(downloadToken.value);
|
||||
}
|
||||
|
|
@ -2242,6 +2287,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
..write('stored: $stored, ')
|
||||
..write('reuploadRequestedBy: $reuploadRequestedBy, ')
|
||||
..write('displayLimitInMilliseconds: $displayLimitInMilliseconds, ')
|
||||
..write('removeAudio: $removeAudio, ')
|
||||
..write('downloadToken: $downloadToken, ')
|
||||
..write('encryptionKey: $encryptionKey, ')
|
||||
..write('encryptionMac: $encryptionMac, ')
|
||||
|
|
@ -7795,6 +7841,7 @@ typedef $$MediaFilesTableCreateCompanionBuilder = MediaFilesCompanion Function({
|
|||
Value<bool> stored,
|
||||
Value<List<int>?> reuploadRequestedBy,
|
||||
Value<int?> displayLimitInMilliseconds,
|
||||
Value<bool?> removeAudio,
|
||||
Value<Uint8List?> downloadToken,
|
||||
Value<Uint8List?> encryptionKey,
|
||||
Value<Uint8List?> encryptionMac,
|
||||
|
|
@ -7812,6 +7859,7 @@ typedef $$MediaFilesTableUpdateCompanionBuilder = MediaFilesCompanion Function({
|
|||
Value<bool> stored,
|
||||
Value<List<int>?> reuploadRequestedBy,
|
||||
Value<int?> displayLimitInMilliseconds,
|
||||
Value<bool?> removeAudio,
|
||||
Value<Uint8List?> downloadToken,
|
||||
Value<Uint8List?> encryptionKey,
|
||||
Value<Uint8List?> encryptionMac,
|
||||
|
|
@ -7887,6 +7935,9 @@ class $$MediaFilesTableFilterComposer
|
|||
column: $table.displayLimitInMilliseconds,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get removeAudio => $composableBuilder(
|
||||
column: $table.removeAudio, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken, builder: (column) => ColumnFilters(column));
|
||||
|
||||
|
|
@ -7966,6 +8017,9 @@ class $$MediaFilesTableOrderingComposer
|
|||
column: $table.displayLimitInMilliseconds,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get removeAudio => $composableBuilder(
|
||||
column: $table.removeAudio, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
|
@ -8025,6 +8079,9 @@ class $$MediaFilesTableAnnotationComposer
|
|||
GeneratedColumn<int> get displayLimitInMilliseconds => $composableBuilder(
|
||||
column: $table.displayLimitInMilliseconds, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get removeAudio => $composableBuilder(
|
||||
column: $table.removeAudio, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken, builder: (column) => column);
|
||||
|
||||
|
|
@ -8094,6 +8151,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
Value<bool> stored = const Value.absent(),
|
||||
Value<List<int>?> reuploadRequestedBy = const Value.absent(),
|
||||
Value<int?> displayLimitInMilliseconds = const Value.absent(),
|
||||
Value<bool?> removeAudio = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<Uint8List?> encryptionKey = const Value.absent(),
|
||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||
|
|
@ -8111,6 +8169,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
stored: stored,
|
||||
reuploadRequestedBy: reuploadRequestedBy,
|
||||
displayLimitInMilliseconds: displayLimitInMilliseconds,
|
||||
removeAudio: removeAudio,
|
||||
downloadToken: downloadToken,
|
||||
encryptionKey: encryptionKey,
|
||||
encryptionMac: encryptionMac,
|
||||
|
|
@ -8128,6 +8187,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
Value<bool> stored = const Value.absent(),
|
||||
Value<List<int>?> reuploadRequestedBy = const Value.absent(),
|
||||
Value<int?> displayLimitInMilliseconds = const Value.absent(),
|
||||
Value<bool?> removeAudio = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<Uint8List?> encryptionKey = const Value.absent(),
|
||||
Value<Uint8List?> encryptionMac = const Value.absent(),
|
||||
|
|
@ -8145,6 +8205,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
stored: stored,
|
||||
reuploadRequestedBy: reuploadRequestedBy,
|
||||
displayLimitInMilliseconds: displayLimitInMilliseconds,
|
||||
removeAudio: removeAudio,
|
||||
downloadToken: downloadToken,
|
||||
encryptionKey: encryptionKey,
|
||||
encryptionMac: encryptionMac,
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ Future<void> startBackgroundMediaUpload(MediaFileService mediaService) async {
|
|||
if (mediaService.mediaFile.uploadState == UploadState.initialized ||
|
||||
mediaService.mediaFile.uploadState == UploadState.preprocessing) {
|
||||
await mediaService.setUploadState(UploadState.preprocessing);
|
||||
|
||||
if (!mediaService.tempPath.existsSync()) {
|
||||
await mediaService.compressMedia();
|
||||
}
|
||||
|
|
@ -87,6 +88,8 @@ Future<void> startBackgroundMediaUpload(MediaFileService mediaService) async {
|
|||
}
|
||||
if (mediaService.uploadRequestPath.existsSync()) {
|
||||
await mediaService.setUploadState(UploadState.uploading);
|
||||
// at this point the original file is not used any more, so it can be deleted
|
||||
mediaService.originalPath.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,23 @@ Future<void> handleContactRequest(
|
|||
switch (contactRequest.type) {
|
||||
case EncryptedContent_ContactRequest_Type.REQUEST:
|
||||
Log.info('Got a contact request from $fromUserId');
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(fromUserId)
|
||||
.getSingleOrNull();
|
||||
if (contact != null) {
|
||||
if (contact.accepted) {
|
||||
// contact was already accepted, so just accept the request in the background.
|
||||
await sendCipherText(
|
||||
contact.userId,
|
||||
EncryptedContent(
|
||||
contactRequest: EncryptedContent_ContactRequest(
|
||||
type: EncryptedContent_ContactRequest_Type.ACCEPT,
|
||||
),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Request the username by the server so an attacker can not
|
||||
// forge the displayed username in the contact request
|
||||
final username = await apiService.getUsername(fromUserId);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
|
||||
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:video_compress/video_compress.dart';
|
||||
|
||||
Future<void> compressImage(
|
||||
File sourceFile,
|
||||
|
|
@ -10,6 +16,8 @@ Future<void> compressImage(
|
|||
) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
// // ffmpeg -i input.png -vcodec libwebp -lossless 1 -preset default output.webp
|
||||
|
||||
try {
|
||||
var compressedBytes = await FlutterImageCompress.compressWithFile(
|
||||
sourceFile.path,
|
||||
|
|
@ -53,42 +61,40 @@ Future<void> compressImage(
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> compressVideo(
|
||||
File sourceFile,
|
||||
File destinationFile,
|
||||
) async {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
MediaInfo? mediaInfo;
|
||||
try {
|
||||
mediaInfo = await VideoCompress.compressVideo(
|
||||
sourceFile.path,
|
||||
quality: VideoQuality.Res1280x720Quality,
|
||||
includeAudio:
|
||||
true, // https://github.com/jonataslaw/VideoCompress/issues/184
|
||||
);
|
||||
|
||||
Log.info('Video has now size of ${mediaInfo!.filesize} bytes.');
|
||||
|
||||
if (mediaInfo.filesize! >= 30 * 1000 * 1000) {
|
||||
// if the media file is over 20MB compress it with low quality
|
||||
mediaInfo = await VideoCompress.compressVideo(
|
||||
sourceFile.path,
|
||||
quality: VideoQuality.Res960x540Quality,
|
||||
includeAudio: true,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error('during video compression: $e');
|
||||
Future<void> compressAndOverlayVideo(MediaFileService media) async {
|
||||
if (media.tempPath.existsSync()) {
|
||||
media.tempPath.deleteSync();
|
||||
}
|
||||
stopwatch.stop();
|
||||
Log.info('It took ${stopwatch.elapsedMilliseconds}ms to compress the video');
|
||||
|
||||
if (mediaInfo == null) {
|
||||
Log.error('Could not compress video using original video.');
|
||||
// as a fall back use the non compressed version
|
||||
sourceFile.copySync(destinationFile.path);
|
||||
final stopwatch = Stopwatch()..start();
|
||||
var command =
|
||||
'-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -map "0:a?" -preset veryfast -crf 28 -c:a aac -b:a 64k "${media.tempPath.path}"';
|
||||
|
||||
if (media.removeAudio) {
|
||||
command =
|
||||
'-i "${media.originalPath.path}" -i "${media.overlayImagePath.path}" -filter_complex "[1:v][0:v]scale2ref=w=ref_w:h=ref_h[ovr][base];[base][ovr]overlay=0:0" -preset veryfast -crf 28 -an "${media.tempPath.path}"';
|
||||
}
|
||||
|
||||
final session = await FFmpegKit.execute(command);
|
||||
final returnCode = await session.getReturnCode();
|
||||
|
||||
if (ReturnCode.isSuccess(returnCode)) {
|
||||
stopwatch.stop();
|
||||
Log.info(
|
||||
'It took ${stopwatch.elapsedMilliseconds}ms to compress the video',
|
||||
);
|
||||
} else {
|
||||
await mediaInfo.file!.copy(destinationFile.path);
|
||||
Log.info(command);
|
||||
Log.error('Compression failed for the video with exit code $returnCode.');
|
||||
Log.error(await session.getAllLogsAsString());
|
||||
// This should not happen, but in case "notify" the user that the video was not send... This is absolutely bad, but
|
||||
// better this way then sending an uncompressed media file which potentially is 100MB big :/
|
||||
// Hopefully the user will report the strange behavior <3
|
||||
await twonlyDB.messagesDao.updateMessagesByMediaId(
|
||||
media.mediaFile.mediaId,
|
||||
const MessagesCompanion(isDeletedFromSender: Value(true)),
|
||||
);
|
||||
media.fullMediaRemoval();
|
||||
await media.setUploadState(UploadState.uploaded);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,22 @@ class MediaFileService {
|
|||
await updateFromDB();
|
||||
}
|
||||
|
||||
bool get removeAudio => mediaFile.removeAudio ?? false;
|
||||
|
||||
Future<void> toggleRemoveAudio() async {
|
||||
// var removeAudio = false;
|
||||
// if (mediaFile.removeAudio != null) {
|
||||
// removeAudio = !mediaFile.removeAudio!;
|
||||
// }
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
MediaFilesCompanion(
|
||||
removeAudio: Value(!removeAudio),
|
||||
),
|
||||
);
|
||||
await updateFromDB();
|
||||
}
|
||||
|
||||
Future<void> setUploadState(UploadState uploadState) async {
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
|
|
@ -160,11 +176,12 @@ class MediaFileService {
|
|||
Log.error('Could not compress as original media does not exists.');
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mediaFile.type) {
|
||||
case MediaType.image:
|
||||
await compressImage(originalPath, tempPath);
|
||||
case MediaType.video:
|
||||
await compressVideo(originalPath, tempPath);
|
||||
await compressAndOverlayVideo(this);
|
||||
case MediaType.gif:
|
||||
originalPath.renameSync(tempPath.path);
|
||||
Log.error('Compression for .gif is not implemented yet.');
|
||||
|
|
@ -266,7 +283,7 @@ class MediaFileService {
|
|||
File get thumbnailPath => _buildFilePath(
|
||||
'stored',
|
||||
namePrefix: '.thumbnail',
|
||||
extensionParam: 'png',
|
||||
extensionParam: 'webp',
|
||||
);
|
||||
File get encryptedPath => _buildFilePath(
|
||||
'tmp',
|
||||
|
|
@ -280,4 +297,9 @@ class MediaFileService {
|
|||
'tmp',
|
||||
namePrefix: '.original',
|
||||
);
|
||||
File get overlayImagePath => _buildFilePath(
|
||||
'tmp',
|
||||
namePrefix: '.overlay',
|
||||
extensionParam: 'png',
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,29 @@
|
|||
import 'dart:io';
|
||||
import 'package:ffmpeg_kit_flutter_new/ffmpeg_kit.dart';
|
||||
import 'package:ffmpeg_kit_flutter_new/return_code.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:video_thumbnail/video_thumbnail.dart';
|
||||
|
||||
Future<void> createThumbnailsForVideo(
|
||||
File sourceFile,
|
||||
File destinationFile,
|
||||
) async {
|
||||
final fileExtension = sourceFile.path.split('.').last.toLowerCase();
|
||||
if (fileExtension != 'mp4') {
|
||||
Log.error('Could not create thumbnail for video. $fileExtension != .mp4');
|
||||
return;
|
||||
}
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
try {
|
||||
await VideoThumbnail.thumbnailFile(
|
||||
video: sourceFile.path,
|
||||
thumbnailPath: destinationFile.path,
|
||||
maxWidth: 450,
|
||||
quality: 75,
|
||||
final command =
|
||||
'-i ${sourceFile.path} -ss 00:00:00 -vframes 1 -vf "scale=iw:ih:flags=lanczos" -c:v libwebp -q:v 100 -compression_level 6 ${destinationFile.path}';
|
||||
|
||||
final session = await FFmpegKit.execute(command);
|
||||
final returnCode = await session.getReturnCode();
|
||||
|
||||
if (ReturnCode.isSuccess(returnCode)) {
|
||||
stopwatch.stop();
|
||||
Log.info(
|
||||
'It took ${stopwatch.elapsedMilliseconds}ms to create the thumbnail.',
|
||||
);
|
||||
} catch (e) {
|
||||
Log.error('Could not create the video thumbnail: $e');
|
||||
} else {
|
||||
Log.info(command);
|
||||
Log.error('Compression failed for the video with exit code $returnCode.');
|
||||
Log.error(await session.getAllLogsAsString());
|
||||
// Report this error to the user?
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ class CameraZoomButtons extends StatefulWidget {
|
|||
final double scaleFactor;
|
||||
final Function updateScaleFactor;
|
||||
final SelectedCameraDetails selectedCameraDetails;
|
||||
final Future<void> Function(int sCameraId, bool init, bool enableAudio)
|
||||
selectCamera;
|
||||
final Future<void> Function(int sCameraId, bool init) selectCamera;
|
||||
|
||||
@override
|
||||
State<CameraZoomButtons> createState() => _CameraZoomButtonsState();
|
||||
|
|
@ -106,7 +105,7 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
),
|
||||
onPressed: () async {
|
||||
if (showWideAngleZoomIOS) {
|
||||
await widget.selectCamera(2, true, false);
|
||||
await widget.selectCamera(2, true);
|
||||
} else {
|
||||
final level = await widget.controller.getMinZoomLevel();
|
||||
widget.updateScaleFactor(level);
|
||||
|
|
@ -130,7 +129,7 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
|||
onPressed: () async {
|
||||
if (showWideAngleZoomIOS &&
|
||||
widget.selectedCameraDetails.cameraId == 2) {
|
||||
await widget.selectCamera(0, true, false);
|
||||
await widget.selectCamera(0, true);
|
||||
} else {
|
||||
widget.updateScaleFactor(1.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,12 @@ 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/home.view.dart';
|
||||
|
||||
int maxVideoRecordingTime = 15;
|
||||
int maxVideoRecordingTime = 60;
|
||||
|
||||
Future<(SelectedCameraDetails, CameraController)?> initializeCameraController(
|
||||
SelectedCameraDetails details,
|
||||
int sCameraId,
|
||||
bool init,
|
||||
bool enableAudio,
|
||||
) async {
|
||||
var cameraId = sCameraId;
|
||||
if (cameraId >= gCameras.length) return null;
|
||||
|
|
@ -49,7 +48,7 @@ Future<(SelectedCameraDetails, CameraController)?> initializeCameraController(
|
|||
final cameraController = CameraController(
|
||||
gCameras[cameraId],
|
||||
ResolutionPreset.high,
|
||||
enableAudio: enableAudio,
|
||||
enableAudio: await Permission.microphone.isGranted,
|
||||
);
|
||||
|
||||
await cameraController.initialize().then((_) async {
|
||||
|
|
@ -93,11 +92,8 @@ class CameraPreviewControllerView extends StatelessWidget {
|
|||
this.sendToGroup,
|
||||
});
|
||||
final Group? sendToGroup;
|
||||
final Future<CameraController?> Function(
|
||||
int sCameraId,
|
||||
bool init,
|
||||
bool enableAudio,
|
||||
) selectCamera;
|
||||
final Future<CameraController?> Function(int sCameraId, bool init)
|
||||
selectCamera;
|
||||
final CameraController? cameraController;
|
||||
final SelectedCameraDetails selectedCameraDetails;
|
||||
final ScreenshotController screenshotController;
|
||||
|
|
@ -119,8 +115,7 @@ class CameraPreviewControllerView extends StatelessWidget {
|
|||
} else {
|
||||
return PermissionHandlerView(
|
||||
onSuccess: () {
|
||||
// setState(() {});
|
||||
selectCamera(0, true, false);
|
||||
selectCamera(0, true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -145,7 +140,6 @@ class CameraPreviewView extends StatefulWidget {
|
|||
final Future<CameraController?> Function(
|
||||
int sCameraId,
|
||||
bool init,
|
||||
bool enableAudio,
|
||||
) selectCamera;
|
||||
final CameraController? cameraController;
|
||||
final SelectedCameraDetails selectedCameraDetails;
|
||||
|
|
@ -156,19 +150,17 @@ class CameraPreviewView extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||
bool sharePreviewIsShown = false;
|
||||
bool galleryLoadedImageIsShown = false;
|
||||
bool showSelfieFlash = false;
|
||||
double basePanY = 0;
|
||||
double baseScaleFactor = 0;
|
||||
bool cameraLoaded = false;
|
||||
bool isVideoRecording = false;
|
||||
bool hasAudioPermission = true;
|
||||
bool videoWithAudio = true;
|
||||
DateTime? videoRecordingStarted;
|
||||
Timer? videoRecordingTimer;
|
||||
bool _sharePreviewIsShown = false;
|
||||
bool _galleryLoadedImageIsShown = false;
|
||||
bool _showSelfieFlash = false;
|
||||
double _basePanY = 0;
|
||||
double _baseScaleFactor = 0;
|
||||
bool _isVideoRecording = false;
|
||||
bool _hasAudioPermission = true;
|
||||
DateTime? _videoRecordingStarted;
|
||||
Timer? _videoRecordingTimer;
|
||||
|
||||
DateTime currentTime = DateTime.now();
|
||||
DateTime _currentTime = DateTime.now();
|
||||
final GlobalKey keyTriggerButton = GlobalKey();
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
|
|
@ -179,9 +171,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
hasAudioPermission = await Permission.microphone.isGranted;
|
||||
_hasAudioPermission = await Permission.microphone.isGranted;
|
||||
|
||||
if (!hasAudioPermission && !gUser.requestedAudioPermission) {
|
||||
if (!_hasAudioPermission && !gUser.requestedAudioPermission) {
|
||||
await updateUserdata((u) {
|
||||
u.requestedAudioPermission = true;
|
||||
return u;
|
||||
|
|
@ -194,7 +186,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
videoRecordingTimer?.cancel();
|
||||
_videoRecordingTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +197,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
if (statuses[Permission.microphone]!.isPermanentlyDenied) {
|
||||
await openAppSettings();
|
||||
} else {
|
||||
hasAudioPermission = await Permission.microphone.isGranted;
|
||||
_hasAudioPermission = await Permission.microphone.isGranted;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
|
@ -248,16 +240,16 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
}
|
||||
|
||||
Future<void> takePicture() async {
|
||||
if (sharePreviewIsShown || isVideoRecording) return;
|
||||
if (_sharePreviewIsShown || _isVideoRecording) return;
|
||||
late Future<Uint8List?> imageBytes;
|
||||
|
||||
setState(() {
|
||||
sharePreviewIsShown = true;
|
||||
_sharePreviewIsShown = true;
|
||||
});
|
||||
if (widget.selectedCameraDetails.isFlashOn) {
|
||||
if (isFront) {
|
||||
setState(() {
|
||||
showSelfieFlash = true;
|
||||
_showSelfieFlash = true;
|
||||
});
|
||||
} else {
|
||||
await widget.cameraController?.setFlashMode(FlashMode.torch);
|
||||
|
|
@ -285,7 +277,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
return;
|
||||
}
|
||||
setState(() {
|
||||
sharePreviewIsShown = false;
|
||||
_sharePreviewIsShown = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +303,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
..deleteSync();
|
||||
|
||||
// Start with compressing the video, to speed up the process in case the video is not changed.
|
||||
unawaited(mediaFileService.compressMedia());
|
||||
// unawaited(mediaFileService.compressMedia());
|
||||
}
|
||||
|
||||
final shouldReturn = await Navigator.push(
|
||||
|
|
@ -333,8 +325,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
) as bool?;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
sharePreviewIsShown = false;
|
||||
showSelfieFlash = false;
|
||||
_sharePreviewIsShown = false;
|
||||
_showSelfieFlash = false;
|
||||
});
|
||||
}
|
||||
if (!mounted) return true;
|
||||
|
|
@ -350,7 +342,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
await widget.selectCamera(
|
||||
widget.selectedCameraDetails.cameraId,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -368,9 +359,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
return;
|
||||
}
|
||||
|
||||
widget.selectedCameraDetails.scaleFactor = (baseScaleFactor +
|
||||
widget.selectedCameraDetails.scaleFactor = (_baseScaleFactor +
|
||||
// ignore: avoid_dynamic_calls
|
||||
(basePanY - (details.localPosition.dy as double)) / 30)
|
||||
(_basePanY - (details.localPosition.dy as double)) / 30)
|
||||
.clamp(1, widget.selectedCameraDetails.maxAvailableZoom);
|
||||
|
||||
await widget.cameraController!
|
||||
|
|
@ -382,8 +373,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
|
||||
Future<void> pickImageFromGallery() async {
|
||||
setState(() {
|
||||
galleryLoadedImageIsShown = true;
|
||||
sharePreviewIsShown = true;
|
||||
_galleryLoadedImageIsShown = true;
|
||||
_sharePreviewIsShown = true;
|
||||
});
|
||||
final picker = ImagePicker();
|
||||
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
|
||||
|
|
@ -397,8 +388,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
);
|
||||
}
|
||||
setState(() {
|
||||
galleryLoadedImageIsShown = false;
|
||||
sharePreviewIsShown = false;
|
||||
_galleryLoadedImageIsShown = false;
|
||||
_sharePreviewIsShown = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -407,41 +398,32 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
widget.cameraController!.value.isRecordingVideo) {
|
||||
return;
|
||||
}
|
||||
var cameraController = widget.cameraController;
|
||||
if (hasAudioPermission && videoWithAudio) {
|
||||
cameraController = await widget.selectCamera(
|
||||
widget.selectedCameraDetails.cameraId,
|
||||
false,
|
||||
await Permission.microphone.isGranted && videoWithAudio,
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
isVideoRecording = true;
|
||||
_isVideoRecording = true;
|
||||
});
|
||||
|
||||
try {
|
||||
await cameraController?.startVideoRecording();
|
||||
videoRecordingTimer =
|
||||
await widget.cameraController?.startVideoRecording();
|
||||
_videoRecordingTimer =
|
||||
Timer.periodic(const Duration(milliseconds: 15), (timer) {
|
||||
setState(() {
|
||||
currentTime = DateTime.now();
|
||||
_currentTime = DateTime.now();
|
||||
});
|
||||
if (videoRecordingStarted != null &&
|
||||
currentTime.difference(videoRecordingStarted!).inSeconds >=
|
||||
if (_videoRecordingStarted != null &&
|
||||
_currentTime.difference(_videoRecordingStarted!).inSeconds >=
|
||||
maxVideoRecordingTime) {
|
||||
timer.cancel();
|
||||
videoRecordingTimer = null;
|
||||
_videoRecordingTimer = null;
|
||||
stopVideoRecording();
|
||||
}
|
||||
});
|
||||
setState(() {
|
||||
videoRecordingStarted = DateTime.now();
|
||||
isVideoRecording = true;
|
||||
_videoRecordingStarted = DateTime.now();
|
||||
_isVideoRecording = true;
|
||||
});
|
||||
} on CameraException catch (e) {
|
||||
setState(() {
|
||||
isVideoRecording = false;
|
||||
_isVideoRecording = false;
|
||||
});
|
||||
_showCameraException(e);
|
||||
return;
|
||||
|
|
@ -449,14 +431,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
}
|
||||
|
||||
Future<void> stopVideoRecording() async {
|
||||
if (videoRecordingTimer != null) {
|
||||
videoRecordingTimer?.cancel();
|
||||
videoRecordingTimer = null;
|
||||
if (_videoRecordingTimer != null) {
|
||||
_videoRecordingTimer?.cancel();
|
||||
_videoRecordingTimer = null;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
videoRecordingStarted = null;
|
||||
isVideoRecording = false;
|
||||
_videoRecordingStarted = null;
|
||||
_isVideoRecording = false;
|
||||
});
|
||||
|
||||
if (widget.cameraController == null ||
|
||||
|
|
@ -465,7 +447,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
}
|
||||
|
||||
setState(() {
|
||||
sharePreviewIsShown = true;
|
||||
_sharePreviewIsShown = true;
|
||||
});
|
||||
|
||||
try {
|
||||
|
|
@ -509,15 +491,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
return;
|
||||
}
|
||||
setState(() {
|
||||
basePanY = details.localPosition.dy;
|
||||
baseScaleFactor = widget.selectedCameraDetails.scaleFactor;
|
||||
_basePanY = details.localPosition.dy;
|
||||
_baseScaleFactor = widget.selectedCameraDetails.scaleFactor;
|
||||
});
|
||||
},
|
||||
onLongPressMoveUpdate: onPanUpdate,
|
||||
onLongPressStart: (details) {
|
||||
setState(() {
|
||||
basePanY = details.localPosition.dy;
|
||||
baseScaleFactor = widget.selectedCameraDetails.scaleFactor;
|
||||
_basePanY = details.localPosition.dy;
|
||||
_baseScaleFactor = widget.selectedCameraDetails.scaleFactor;
|
||||
});
|
||||
// Get the position of the pointer
|
||||
final renderBox =
|
||||
|
|
@ -540,7 +522,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
onPanUpdate: onPanUpdate,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (galleryLoadedImageIsShown)
|
||||
if (_galleryLoadedImageIsShown)
|
||||
Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
|
|
@ -551,11 +533,11 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (!sharePreviewIsShown &&
|
||||
if (!_sharePreviewIsShown &&
|
||||
widget.sendToGroup != null &&
|
||||
!isVideoRecording)
|
||||
!_isVideoRecording)
|
||||
SendToWidget(sendTo: widget.sendToGroup!.groupName),
|
||||
if (!sharePreviewIsShown && !isVideoRecording)
|
||||
if (!_sharePreviewIsShown && !_isVideoRecording)
|
||||
Positioned(
|
||||
right: 5,
|
||||
top: 0,
|
||||
|
|
@ -573,7 +555,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
await widget.selectCamera(
|
||||
(widget.selectedCameraDetails.cameraId + 1) % 2,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
@ -598,7 +579,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
setState(() {});
|
||||
},
|
||||
),
|
||||
if (!hasAudioPermission)
|
||||
if (!_hasAudioPermission)
|
||||
ActionButton(
|
||||
Icons.mic_off_rounded,
|
||||
color: Colors.white.withAlpha(160),
|
||||
|
|
@ -606,27 +587,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
'Allow microphone access for video recording.',
|
||||
onPressed: requestMicrophonePermission,
|
||||
),
|
||||
if (hasAudioPermission)
|
||||
ActionButton(
|
||||
videoWithAudio
|
||||
? Icons.volume_up_rounded
|
||||
: Icons.volume_off_rounded,
|
||||
tooltipText: 'Record video with audio.',
|
||||
color: videoWithAudio
|
||||
? Colors.white
|
||||
: Colors.white.withAlpha(160),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
videoWithAudio = !videoWithAudio;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!sharePreviewIsShown)
|
||||
if (!_sharePreviewIsShown)
|
||||
Positioned(
|
||||
bottom: 30,
|
||||
left: 0,
|
||||
|
|
@ -638,7 +604,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
if (widget.cameraController!.value.isInitialized &&
|
||||
widget.selectedCameraDetails.isZoomAble &&
|
||||
!isFront &&
|
||||
!isVideoRecording)
|
||||
!_isVideoRecording)
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: CameraZoomButtons(
|
||||
|
|
@ -655,7 +621,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (!isVideoRecording)
|
||||
if (!_isVideoRecording)
|
||||
GestureDetector(
|
||||
onTap: pickImageFromGallery,
|
||||
child: Align(
|
||||
|
|
@ -687,7 +653,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
width: 7,
|
||||
color: isVideoRecording
|
||||
color: _isVideoRecording
|
||||
? Colors.red
|
||||
: Colors.white,
|
||||
),
|
||||
|
|
@ -695,7 +661,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (!isVideoRecording) const SizedBox(width: 80),
|
||||
if (!_isVideoRecording) const SizedBox(width: 80),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
@ -703,10 +669,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
),
|
||||
),
|
||||
VideoRecordingTimer(
|
||||
videoRecordingStarted: videoRecordingStarted,
|
||||
videoRecordingStarted: _videoRecordingStarted,
|
||||
maxVideoRecordingTime: maxVideoRecordingTime,
|
||||
),
|
||||
if (!sharePreviewIsShown && widget.sendToGroup != null)
|
||||
if (!_sharePreviewIsShown && widget.sendToGroup != null)
|
||||
Positioned(
|
||||
left: 5,
|
||||
top: 10,
|
||||
|
|
@ -718,7 +684,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
},
|
||||
),
|
||||
),
|
||||
if (showSelfieFlash)
|
||||
if (_showSelfieFlash)
|
||||
Positioned.fill(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(22),
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
unawaited(selectCamera(0, true, false));
|
||||
unawaited(selectCamera(0, true));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -36,13 +36,11 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
|||
Future<CameraController?> selectCamera(
|
||||
int sCameraId,
|
||||
bool init,
|
||||
bool enableAudio,
|
||||
) async {
|
||||
final opts = await initializeCameraController(
|
||||
selectedCameraDetails,
|
||||
sCameraId,
|
||||
init,
|
||||
enableAudio,
|
||||
);
|
||||
if (opts != null) {
|
||||
selectedCameraDetails = opts.$1;
|
||||
|
|
@ -61,7 +59,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
|||
}
|
||||
await cameraController!.dispose();
|
||||
cameraController = null;
|
||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
|
||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ class BackgroundLayerData extends Layer {
|
|||
ImageItem image;
|
||||
}
|
||||
|
||||
class FilterLayerData extends Layer {}
|
||||
class FilterLayerData extends Layer {
|
||||
int page = 1;
|
||||
}
|
||||
|
||||
/// Attributes used by [EmojiLayer]
|
||||
class EmojiLayerData extends Layer {
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ class _FilterLayerState extends State<FilterLayer> {
|
|||
}
|
||||
},
|
||||
onPageChanged: (index) {
|
||||
widget.layerData.page = index;
|
||||
if (index == 0) {
|
||||
// If the user swipes to the first duplicated page, jump to the last page
|
||||
pageController.jumpToPage(pages.length);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:collection';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||
|
|
@ -148,9 +149,7 @@ class UserCheckbox extends StatelessWidget {
|
|||
Row(
|
||||
children: [
|
||||
Text(
|
||||
group.groupName.length > 12
|
||||
? '${group.groupName.substring(0, 9)}...'
|
||||
: group.groupName,
|
||||
substringBy(group.groupName, 12),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -211,6 +211,25 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
},
|
||||
),
|
||||
),
|
||||
if (media.type == MediaType.video)
|
||||
ActionButton(
|
||||
(mediaService.removeAudio)
|
||||
? Icons.volume_off_rounded
|
||||
: Icons.volume_up_rounded,
|
||||
tooltipText: 'Enable Audio in Video',
|
||||
color: (mediaService.removeAudio)
|
||||
? Colors.white.withAlpha(160)
|
||||
: Colors.white,
|
||||
onPressed: () async {
|
||||
await mediaService.toggleRemoveAudio();
|
||||
if (mediaService.removeAudio) {
|
||||
await videoController?.setVolume(0);
|
||||
} else {
|
||||
await videoController?.setVolume(100);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ActionButton(
|
||||
FontAwesomeIcons.shieldHeart,
|
||||
|
|
@ -281,8 +300,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
}
|
||||
|
||||
Future<void> pushShareImageView() async {
|
||||
final mediaStoreFuture =
|
||||
(media.type == MediaType.image) ? storeImageAsOriginal() : null;
|
||||
final mediaStoreFuture = storeImageAsOriginal();
|
||||
|
||||
await videoController?.pause();
|
||||
if (isDisposed || !mounted) return;
|
||||
|
|
@ -312,33 +330,39 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
}
|
||||
}
|
||||
|
||||
if (layers.length > 1 || media.type == MediaType.video) {
|
||||
for (final x in layers) {
|
||||
x.showCustomButtons = false;
|
||||
}
|
||||
setState(() {});
|
||||
final image = await screenshotController.capture(
|
||||
pixelRatio: pixelRatio,
|
||||
);
|
||||
if (image == null) {
|
||||
Log.error('screenshotController did not return image bytes');
|
||||
return null;
|
||||
}
|
||||
|
||||
for (final x in layers) {
|
||||
x.showCustomButtons = true;
|
||||
}
|
||||
setState(() {});
|
||||
return image;
|
||||
for (final x in layers) {
|
||||
x.showCustomButtons = false;
|
||||
}
|
||||
setState(() {});
|
||||
final image = await screenshotController.capture(
|
||||
pixelRatio: pixelRatio,
|
||||
);
|
||||
if (image == null) {
|
||||
Log.error('screenshotController did not return image bytes');
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
for (final x in layers) {
|
||||
x.showCustomButtons = true;
|
||||
}
|
||||
setState(() {});
|
||||
return image;
|
||||
}
|
||||
|
||||
Future<bool> storeImageAsOriginal() async {
|
||||
if (mediaService.overlayImagePath.existsSync()) {
|
||||
mediaService.overlayImagePath.deleteSync();
|
||||
}
|
||||
if (mediaService.tempPath.existsSync()) {
|
||||
mediaService.tempPath.deleteSync();
|
||||
}
|
||||
final imageBytes = await getEditedImageBytes();
|
||||
if (imageBytes == null) return false;
|
||||
mediaService.originalPath.writeAsBytesSync(imageBytes);
|
||||
if (media.type == MediaType.image) {
|
||||
mediaService.originalPath.writeAsBytesSync(imageBytes);
|
||||
} else {
|
||||
mediaService.overlayImagePath.writeAsBytesSync(imageBytes);
|
||||
}
|
||||
|
||||
// In case the image was already stored, then rename the stored image.
|
||||
if (mediaService.storedPath.existsSync()) {
|
||||
|
|
@ -373,12 +397,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
sendingOrLoadingImage = true;
|
||||
});
|
||||
|
||||
if (media.type == MediaType.image) {
|
||||
await storeImageAsOriginal();
|
||||
}
|
||||
if (media.type == MediaType.video) {
|
||||
Log.error('TODO: COMBINE VIDEO AND IMAGE!!!');
|
||||
}
|
||||
await storeImageAsOriginal();
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'dart:collection';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/daos/groups.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||
|
|
@ -64,7 +65,7 @@ class _ShareImageView extends State<ShareImageView> {
|
|||
await widget.mediaStoreFuture;
|
||||
}
|
||||
mediaStoreFutureReady = true;
|
||||
unawaited(startBackgroundMediaUpload(widget.mediaFileService));
|
||||
// unawaited(startBackgroundMediaUpload(widget.mediaFileService));
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
}
|
||||
|
|
@ -323,7 +324,7 @@ class UserList extends StatelessWidget {
|
|||
return ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
Text(group.groupName),
|
||||
Text(substringBy(group.groupName, 12)),
|
||||
FlameCounterWidget(
|
||||
groupId: group.groupId,
|
||||
prefix: true,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class ChatTextEntry extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var text = message.content ?? '';
|
||||
var textColor = Colors.white;
|
||||
|
||||
if (EmojiAnimation.supported(text)) {
|
||||
return Container(
|
||||
|
|
@ -59,7 +60,10 @@ class ChatTextEntry extends StatelessWidget {
|
|||
|
||||
if (message.isDeletedFromSender) {
|
||||
text = context.lang.messageWasDeleted;
|
||||
color = Colors.grey;
|
||||
color = isDarkMode(context) ? Colors.black : Colors.grey;
|
||||
if (isDarkMode(context)) {
|
||||
textColor = const Color.fromARGB(255, 99, 99, 99);
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
|
|
@ -78,10 +82,10 @@ class ChatTextEntry extends StatelessWidget {
|
|||
children: [
|
||||
if (expanded)
|
||||
Expanded(
|
||||
child: BetterText(text: text),
|
||||
child: BetterText(text: text, textColor: textColor),
|
||||
)
|
||||
else ...[
|
||||
BetterText(text: text),
|
||||
BetterText(text: text, textColor: textColor),
|
||||
SizedBox(
|
||||
width: spacerWidth,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import 'package:twonly/src/utils/log.dart';
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class BetterText extends StatelessWidget {
|
||||
const BetterText({required this.text, super.key});
|
||||
const BetterText({required this.text, required this.textColor, super.key});
|
||||
final String text;
|
||||
final Color textColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -65,8 +66,8 @@ class BetterText extends StatelessWidget {
|
|||
softWrap: true,
|
||||
textAlign: TextAlign.start,
|
||||
overflow: TextOverflow.visible,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 17,
|
||||
decoration: TextDecoration.none,
|
||||
fontWeight: FontWeight.normal,
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class HomeViewState extends State<HomeView> {
|
|||
}
|
||||
if (cameraController == null && !initCameraStarted && offsetRatio < 1) {
|
||||
initCameraStarted = true;
|
||||
unawaited(selectCamera(selectedCameraDetails.cameraId, false, false));
|
||||
unawaited(selectCamera(selectedCameraDetails.cameraId, false));
|
||||
}
|
||||
if (offsetRatio == 1) {
|
||||
disableCameraTimer = Timer(const Duration(milliseconds: 500), () async {
|
||||
|
|
@ -97,7 +97,7 @@ class HomeViewState extends State<HomeView> {
|
|||
.listen((NotificationResponse? response) async {
|
||||
globalUpdateOfHomeViewPageIndex(0);
|
||||
});
|
||||
unawaited(selectCamera(0, true, false));
|
||||
unawaited(selectCamera(0, true));
|
||||
unawaited(initAsync());
|
||||
}
|
||||
|
||||
|
|
@ -109,16 +109,11 @@ class HomeViewState extends State<HomeView> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<CameraController?> selectCamera(
|
||||
int sCameraId,
|
||||
bool init,
|
||||
bool enableAudio,
|
||||
) async {
|
||||
Future<CameraController?> selectCamera(int sCameraId, bool init) async {
|
||||
final opts = await initializeCameraController(
|
||||
selectedCameraDetails,
|
||||
sCameraId,
|
||||
init,
|
||||
enableAudio,
|
||||
);
|
||||
if (opts != null) {
|
||||
selectedCameraDetails = opts.$1;
|
||||
|
|
@ -138,7 +133,7 @@ class HomeViewState extends State<HomeView> {
|
|||
}
|
||||
await cameraController!.dispose();
|
||||
cameraController = null;
|
||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
|
||||
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false);
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@ class _DatabaseMigrationViewState extends State<DatabaseMigrationView> {
|
|||
children: [
|
||||
const SizedBox(height: 40),
|
||||
const Text(
|
||||
'twonly. Jetzt besser als je zuvor.',
|
||||
'twonly. Besser als je zuvor.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 35,
|
||||
|
|
|
|||
36
pubspec.lock
36
pubspec.lock
|
|
@ -29,10 +29,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0
|
||||
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.4.0"
|
||||
version: "8.4.1"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -157,10 +157,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: camera
|
||||
sha256: "87a27e0553e3432119c1c2f6e4b9a1bbf7d2c660552b910bfa59185a9facd632"
|
||||
sha256: eefad89f262a873f38d21e5eec853461737ea074d7c9ede39f3ceb135d201cab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.2+1"
|
||||
version: "0.11.3"
|
||||
camera_android_camerax:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
|
|
@ -402,6 +402,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
ffmpeg_kit_flutter_new:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ffmpeg_kit_flutter_new
|
||||
sha256: d127635f27e93a7f21f0a14ce0a1a148e80919c402dac4a2118d73bfb17ce841
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
ffmpeg_kit_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffmpeg_kit_flutter_platform_interface
|
||||
sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -850,10 +866,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_android
|
||||
sha256: "58a85e6f09fe9c4484d53d18a0bd6271b72c53fce1d05e6f745ae36d8c18efca"
|
||||
sha256: ca2a3b04d34e76157e9ae680ef16014fb4c2d20484e78417eaed6139330056f6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.13+5"
|
||||
version: "0.8.13+7"
|
||||
image_picker_for_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1771,14 +1787,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
video_compress:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: video_compress
|
||||
sha256: "31bc5cdb9a02ba666456e5e1907393c28e6e0e972980d7d8d619a7beda0d4f20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
video_player:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ dependencies:
|
|||
device_info_plus: ^12.1.0
|
||||
drift: ^2.25.1
|
||||
drift_flutter: ^0.2.4
|
||||
ffmpeg_kit_flutter_new: ^4.1.0
|
||||
firebase_core: ^4.2.0
|
||||
firebase_messaging: ^16.0.3
|
||||
fixnum: ^1.1.1
|
||||
|
|
@ -67,7 +68,6 @@ dependencies:
|
|||
share_plus: ^12.0.0
|
||||
tutorial_coach_mark: ^1.3.0
|
||||
url_launcher: ^6.3.1
|
||||
video_compress: ^3.1.4
|
||||
video_player: ^2.9.5
|
||||
video_thumbnail: ^0.5.6
|
||||
web_socket_channel: ^3.0.1
|
||||
|
|
|
|||
Loading…
Reference in a new issue