integrating ffmpeg

This commit is contained in:
otsmr 2025-10-28 23:26:24 +01:00
parent bd4c30ed4d
commit 35152cce23
23 changed files with 351 additions and 239 deletions

View file

@ -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

View file

@ -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

View file

@ -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()();

View file

@ -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,

View file

@ -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();
} }
} }

View file

@ -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);

View file

@ -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);
} }
} }

View file

@ -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',
);
} }

View file

@ -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?
} }
} }

View file

@ -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);
} }

View file

@ -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),

View file

@ -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

View file

@ -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 {

View file

@ -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);

View file

@ -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,
), ),
], ],

View file

@ -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;

View file

@ -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,

View file

@ -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,
), ),

View file

@ -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,

View file

@ -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 {

View file

@ -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,

View file

@ -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:

View file

@ -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