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