mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 17:28:40 +00:00
starting with rewriting the upload process
This commit is contained in:
parent
f5cbcf154b
commit
b2dc384465
36 changed files with 557 additions and 648 deletions
|
|
@ -6,7 +6,7 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/components/app_outdated.dart';
|
import 'package:twonly/src/views/components/app_outdated.dart';
|
||||||
import 'package:twonly/src/views/home.view.dart';
|
import 'package:twonly/src/views/home.view.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/services/api.service.dart';
|
import 'package:twonly/src/services/api.service.dart';
|
||||||
|
|
||||||
late ApiService apiService;
|
late ApiService apiService;
|
||||||
|
|
@ -9,6 +10,10 @@ late TwonlyDB twonlyDB;
|
||||||
|
|
||||||
List<CameraDescription> gCameras = <CameraDescription>[];
|
List<CameraDescription> gCameras = <CameraDescription>[];
|
||||||
|
|
||||||
|
// Cached UserData in the memory. Every time the user data is changed the `updateUserdata` function is called,
|
||||||
|
// which will update this global variable. The variable is set in the main.dart and after the user has registered in the register.view.dart
|
||||||
|
late UserData gUser;
|
||||||
|
|
||||||
// The following global function can be called from anywhere to update
|
// The following global function can be called from anywhere to update
|
||||||
// the UI when something changed. The callbacks will be set by
|
// the UI when something changed. The callbacks will be set by
|
||||||
// App widget.
|
// App widget.
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/image_editor.provider.dart';
|
import 'package:twonly/src/providers/image_editor.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/api.service.dart';
|
import 'package:twonly/src/services/api.service.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/fcm.service.dart';
|
import 'package:twonly/src/services/fcm.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||||
|
|
@ -28,6 +28,7 @@ void main() async {
|
||||||
|
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
gUser = user;
|
||||||
if (user.isDemoUser) {
|
if (user.isDemoUser) {
|
||||||
await deleteLocalUserData();
|
await deleteLocalUserData();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'messages.dao.g.dart';
|
part 'messages.dao.g.dart';
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,21 @@ enum MediaType {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum UploadState {
|
enum UploadState {
|
||||||
pending,
|
// Image/Video was taken. A database entry was created to track it...
|
||||||
readyToUpload,
|
initialized,
|
||||||
uploadTaskStarted,
|
// Image was stored but not send
|
||||||
receiverNotified,
|
storedOnly,
|
||||||
|
// At this point the user is finished with editing, and the media file can be uploaded
|
||||||
|
compressing,
|
||||||
|
encrypting,
|
||||||
|
uploading,
|
||||||
|
backgroundUploadTaskStarted,
|
||||||
|
uploaded,
|
||||||
|
|
||||||
|
uploadLimitReached,
|
||||||
|
// readyToUpload,
|
||||||
|
// uploadTaskStarted,
|
||||||
|
// receiverNotified,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DownloadState {
|
enum DownloadState {
|
||||||
|
|
@ -33,7 +44,8 @@ class MediaFiles extends Table {
|
||||||
TextColumn get uploadState => textEnum<UploadState>().nullable()();
|
TextColumn get uploadState => textEnum<UploadState>().nullable()();
|
||||||
TextColumn get downloadState => textEnum<DownloadState>().nullable()();
|
TextColumn get downloadState => textEnum<DownloadState>().nullable()();
|
||||||
|
|
||||||
BoolColumn get requiresAuthentication => boolean()();
|
BoolColumn get requiresAuthentication =>
|
||||||
|
boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get reopenByContact =>
|
BoolColumn get reopenByContact =>
|
||||||
boolean().withDefault(const Constant(false))();
|
boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ class Messages extends Table {
|
||||||
TextColumn get mediaId =>
|
TextColumn get mediaId =>
|
||||||
text().nullable().references(MediaFiles, #mediaId)();
|
text().nullable().references(MediaFiles, #mediaId)();
|
||||||
|
|
||||||
|
BlobColumn get downloadToken => blob().nullable()();
|
||||||
|
|
||||||
TextColumn get quotesMessageId =>
|
TextColumn get quotesMessageId =>
|
||||||
text().nullable().references(Messages, #messageId)();
|
text().nullable().references(Messages, #messageId)();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1460,9 +1460,10 @@ class $MediaFilesTable extends MediaFiles
|
||||||
late final GeneratedColumn<bool> requiresAuthentication =
|
late final GeneratedColumn<bool> requiresAuthentication =
|
||||||
GeneratedColumn<bool>('requires_authentication', aliasedName, false,
|
GeneratedColumn<bool>('requires_authentication', aliasedName, false,
|
||||||
type: DriftSqlType.bool,
|
type: DriftSqlType.bool,
|
||||||
requiredDuringInsert: true,
|
requiredDuringInsert: false,
|
||||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
'CHECK ("requires_authentication" IN (0, 1))'));
|
'CHECK ("requires_authentication" IN (0, 1))'),
|
||||||
|
defaultValue: const Constant(false));
|
||||||
static const VerificationMeta _reopenByContactMeta =
|
static const VerificationMeta _reopenByContactMeta =
|
||||||
const VerificationMeta('reopenByContact');
|
const VerificationMeta('reopenByContact');
|
||||||
@override
|
@override
|
||||||
|
|
@ -1563,8 +1564,6 @@ class $MediaFilesTable extends MediaFiles
|
||||||
_requiresAuthenticationMeta,
|
_requiresAuthenticationMeta,
|
||||||
requiresAuthentication.isAcceptableOrUnknown(
|
requiresAuthentication.isAcceptableOrUnknown(
|
||||||
data['requires_authentication']!, _requiresAuthenticationMeta));
|
data['requires_authentication']!, _requiresAuthenticationMeta));
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_requiresAuthenticationMeta);
|
|
||||||
}
|
}
|
||||||
if (data.containsKey('reopen_by_contact')) {
|
if (data.containsKey('reopen_by_contact')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
|
|
@ -2017,7 +2016,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
required MediaType type,
|
required MediaType type,
|
||||||
this.uploadState = const Value.absent(),
|
this.uploadState = const Value.absent(),
|
||||||
this.downloadState = const Value.absent(),
|
this.downloadState = const Value.absent(),
|
||||||
required bool requiresAuthentication,
|
this.requiresAuthentication = const Value.absent(),
|
||||||
this.reopenByContact = const Value.absent(),
|
this.reopenByContact = const Value.absent(),
|
||||||
this.stored = const Value.absent(),
|
this.stored = const Value.absent(),
|
||||||
this.reuploadRequestedBy = const Value.absent(),
|
this.reuploadRequestedBy = const Value.absent(),
|
||||||
|
|
@ -2028,8 +2027,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
||||||
this.encryptionNonce = const Value.absent(),
|
this.encryptionNonce = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : type = Value(type),
|
}) : type = Value(type);
|
||||||
requiresAuthentication = Value(requiresAuthentication);
|
|
||||||
static Insertable<MediaFile> custom({
|
static Insertable<MediaFile> custom({
|
||||||
Expression<String>? mediaId,
|
Expression<String>? mediaId,
|
||||||
Expression<String>? type,
|
Expression<String>? type,
|
||||||
|
|
@ -2233,6 +2231,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
'REFERENCES media_files (media_id)'));
|
'REFERENCES media_files (media_id)'));
|
||||||
|
static const VerificationMeta _downloadTokenMeta =
|
||||||
|
const VerificationMeta('downloadToken');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<Uint8List> downloadToken =
|
||||||
|
GeneratedColumn<Uint8List>('download_token', aliasedName, true,
|
||||||
|
type: DriftSqlType.blob, requiredDuringInsert: false);
|
||||||
static const VerificationMeta _quotesMessageIdMeta =
|
static const VerificationMeta _quotesMessageIdMeta =
|
||||||
const VerificationMeta('quotesMessageId');
|
const VerificationMeta('quotesMessageId');
|
||||||
@override
|
@override
|
||||||
|
|
@ -2319,6 +2323,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
senderId,
|
senderId,
|
||||||
content,
|
content,
|
||||||
mediaId,
|
mediaId,
|
||||||
|
downloadToken,
|
||||||
quotesMessageId,
|
quotesMessageId,
|
||||||
isDeletedFromSender,
|
isDeletedFromSender,
|
||||||
isEdited,
|
isEdited,
|
||||||
|
|
@ -2361,6 +2366,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
context.handle(_mediaIdMeta,
|
context.handle(_mediaIdMeta,
|
||||||
mediaId.isAcceptableOrUnknown(data['media_id']!, _mediaIdMeta));
|
mediaId.isAcceptableOrUnknown(data['media_id']!, _mediaIdMeta));
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('download_token')) {
|
||||||
|
context.handle(
|
||||||
|
_downloadTokenMeta,
|
||||||
|
downloadToken.isAcceptableOrUnknown(
|
||||||
|
data['download_token']!, _downloadTokenMeta));
|
||||||
|
}
|
||||||
if (data.containsKey('quotes_message_id')) {
|
if (data.containsKey('quotes_message_id')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_quotesMessageIdMeta,
|
_quotesMessageIdMeta,
|
||||||
|
|
@ -2428,6 +2439,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}content']),
|
.read(DriftSqlType.string, data['${effectivePrefix}content']),
|
||||||
mediaId: attachedDatabase.typeMapping
|
mediaId: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}media_id']),
|
.read(DriftSqlType.string, data['${effectivePrefix}media_id']),
|
||||||
|
downloadToken: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.blob, data['${effectivePrefix}download_token']),
|
||||||
quotesMessageId: attachedDatabase.typeMapping.read(
|
quotesMessageId: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.string, data['${effectivePrefix}quotes_message_id']),
|
DriftSqlType.string, data['${effectivePrefix}quotes_message_id']),
|
||||||
isDeletedFromSender: attachedDatabase.typeMapping.read(
|
isDeletedFromSender: attachedDatabase.typeMapping.read(
|
||||||
|
|
@ -2461,6 +2474,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
final int? senderId;
|
final int? senderId;
|
||||||
final String? content;
|
final String? content;
|
||||||
final String? mediaId;
|
final String? mediaId;
|
||||||
|
final Uint8List? downloadToken;
|
||||||
final String? quotesMessageId;
|
final String? quotesMessageId;
|
||||||
final bool isDeletedFromSender;
|
final bool isDeletedFromSender;
|
||||||
final bool isEdited;
|
final bool isEdited;
|
||||||
|
|
@ -2476,6 +2490,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
this.senderId,
|
this.senderId,
|
||||||
this.content,
|
this.content,
|
||||||
this.mediaId,
|
this.mediaId,
|
||||||
|
this.downloadToken,
|
||||||
this.quotesMessageId,
|
this.quotesMessageId,
|
||||||
required this.isDeletedFromSender,
|
required this.isDeletedFromSender,
|
||||||
required this.isEdited,
|
required this.isEdited,
|
||||||
|
|
@ -2499,6 +2514,9 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
if (!nullToAbsent || mediaId != null) {
|
if (!nullToAbsent || mediaId != null) {
|
||||||
map['media_id'] = Variable<String>(mediaId);
|
map['media_id'] = Variable<String>(mediaId);
|
||||||
}
|
}
|
||||||
|
if (!nullToAbsent || downloadToken != null) {
|
||||||
|
map['download_token'] = Variable<Uint8List>(downloadToken);
|
||||||
|
}
|
||||||
if (!nullToAbsent || quotesMessageId != null) {
|
if (!nullToAbsent || quotesMessageId != null) {
|
||||||
map['quotes_message_id'] = Variable<String>(quotesMessageId);
|
map['quotes_message_id'] = Variable<String>(quotesMessageId);
|
||||||
}
|
}
|
||||||
|
|
@ -2530,6 +2548,9 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
mediaId: mediaId == null && nullToAbsent
|
mediaId: mediaId == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(mediaId),
|
: Value(mediaId),
|
||||||
|
downloadToken: downloadToken == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(downloadToken),
|
||||||
quotesMessageId: quotesMessageId == null && nullToAbsent
|
quotesMessageId: quotesMessageId == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(quotesMessageId),
|
: Value(quotesMessageId),
|
||||||
|
|
@ -2557,6 +2578,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
senderId: serializer.fromJson<int?>(json['senderId']),
|
senderId: serializer.fromJson<int?>(json['senderId']),
|
||||||
content: serializer.fromJson<String?>(json['content']),
|
content: serializer.fromJson<String?>(json['content']),
|
||||||
mediaId: serializer.fromJson<String?>(json['mediaId']),
|
mediaId: serializer.fromJson<String?>(json['mediaId']),
|
||||||
|
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
||||||
quotesMessageId: serializer.fromJson<String?>(json['quotesMessageId']),
|
quotesMessageId: serializer.fromJson<String?>(json['quotesMessageId']),
|
||||||
isDeletedFromSender:
|
isDeletedFromSender:
|
||||||
serializer.fromJson<bool>(json['isDeletedFromSender']),
|
serializer.fromJson<bool>(json['isDeletedFromSender']),
|
||||||
|
|
@ -2578,6 +2600,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
'senderId': serializer.toJson<int?>(senderId),
|
'senderId': serializer.toJson<int?>(senderId),
|
||||||
'content': serializer.toJson<String?>(content),
|
'content': serializer.toJson<String?>(content),
|
||||||
'mediaId': serializer.toJson<String?>(mediaId),
|
'mediaId': serializer.toJson<String?>(mediaId),
|
||||||
|
'downloadToken': serializer.toJson<Uint8List?>(downloadToken),
|
||||||
'quotesMessageId': serializer.toJson<String?>(quotesMessageId),
|
'quotesMessageId': serializer.toJson<String?>(quotesMessageId),
|
||||||
'isDeletedFromSender': serializer.toJson<bool>(isDeletedFromSender),
|
'isDeletedFromSender': serializer.toJson<bool>(isDeletedFromSender),
|
||||||
'isEdited': serializer.toJson<bool>(isEdited),
|
'isEdited': serializer.toJson<bool>(isEdited),
|
||||||
|
|
@ -2596,6 +2619,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
Value<int?> senderId = const Value.absent(),
|
Value<int?> senderId = const Value.absent(),
|
||||||
Value<String?> content = const Value.absent(),
|
Value<String?> content = const Value.absent(),
|
||||||
Value<String?> mediaId = const Value.absent(),
|
Value<String?> mediaId = const Value.absent(),
|
||||||
|
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||||
Value<String?> quotesMessageId = const Value.absent(),
|
Value<String?> quotesMessageId = const Value.absent(),
|
||||||
bool? isDeletedFromSender,
|
bool? isDeletedFromSender,
|
||||||
bool? isEdited,
|
bool? isEdited,
|
||||||
|
|
@ -2611,6 +2635,8 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
senderId: senderId.present ? senderId.value : this.senderId,
|
senderId: senderId.present ? senderId.value : this.senderId,
|
||||||
content: content.present ? content.value : this.content,
|
content: content.present ? content.value : this.content,
|
||||||
mediaId: mediaId.present ? mediaId.value : this.mediaId,
|
mediaId: mediaId.present ? mediaId.value : this.mediaId,
|
||||||
|
downloadToken:
|
||||||
|
downloadToken.present ? downloadToken.value : this.downloadToken,
|
||||||
quotesMessageId: quotesMessageId.present
|
quotesMessageId: quotesMessageId.present
|
||||||
? quotesMessageId.value
|
? quotesMessageId.value
|
||||||
: this.quotesMessageId,
|
: this.quotesMessageId,
|
||||||
|
|
@ -2630,6 +2656,9 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
senderId: data.senderId.present ? data.senderId.value : this.senderId,
|
senderId: data.senderId.present ? data.senderId.value : this.senderId,
|
||||||
content: data.content.present ? data.content.value : this.content,
|
content: data.content.present ? data.content.value : this.content,
|
||||||
mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId,
|
mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId,
|
||||||
|
downloadToken: data.downloadToken.present
|
||||||
|
? data.downloadToken.value
|
||||||
|
: this.downloadToken,
|
||||||
quotesMessageId: data.quotesMessageId.present
|
quotesMessageId: data.quotesMessageId.present
|
||||||
? data.quotesMessageId.value
|
? data.quotesMessageId.value
|
||||||
: this.quotesMessageId,
|
: this.quotesMessageId,
|
||||||
|
|
@ -2658,6 +2687,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
..write('senderId: $senderId, ')
|
..write('senderId: $senderId, ')
|
||||||
..write('content: $content, ')
|
..write('content: $content, ')
|
||||||
..write('mediaId: $mediaId, ')
|
..write('mediaId: $mediaId, ')
|
||||||
|
..write('downloadToken: $downloadToken, ')
|
||||||
..write('quotesMessageId: $quotesMessageId, ')
|
..write('quotesMessageId: $quotesMessageId, ')
|
||||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||||
..write('isEdited: $isEdited, ')
|
..write('isEdited: $isEdited, ')
|
||||||
|
|
@ -2678,6 +2708,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
senderId,
|
senderId,
|
||||||
content,
|
content,
|
||||||
mediaId,
|
mediaId,
|
||||||
|
$driftBlobEquality.hash(downloadToken),
|
||||||
quotesMessageId,
|
quotesMessageId,
|
||||||
isDeletedFromSender,
|
isDeletedFromSender,
|
||||||
isEdited,
|
isEdited,
|
||||||
|
|
@ -2696,6 +2727,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
other.senderId == this.senderId &&
|
other.senderId == this.senderId &&
|
||||||
other.content == this.content &&
|
other.content == this.content &&
|
||||||
other.mediaId == this.mediaId &&
|
other.mediaId == this.mediaId &&
|
||||||
|
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
||||||
other.quotesMessageId == this.quotesMessageId &&
|
other.quotesMessageId == this.quotesMessageId &&
|
||||||
other.isDeletedFromSender == this.isDeletedFromSender &&
|
other.isDeletedFromSender == this.isDeletedFromSender &&
|
||||||
other.isEdited == this.isEdited &&
|
other.isEdited == this.isEdited &&
|
||||||
|
|
@ -2713,6 +2745,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
final Value<int?> senderId;
|
final Value<int?> senderId;
|
||||||
final Value<String?> content;
|
final Value<String?> content;
|
||||||
final Value<String?> mediaId;
|
final Value<String?> mediaId;
|
||||||
|
final Value<Uint8List?> downloadToken;
|
||||||
final Value<String?> quotesMessageId;
|
final Value<String?> quotesMessageId;
|
||||||
final Value<bool> isDeletedFromSender;
|
final Value<bool> isDeletedFromSender;
|
||||||
final Value<bool> isEdited;
|
final Value<bool> isEdited;
|
||||||
|
|
@ -2729,6 +2762,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
this.senderId = const Value.absent(),
|
this.senderId = const Value.absent(),
|
||||||
this.content = const Value.absent(),
|
this.content = const Value.absent(),
|
||||||
this.mediaId = const Value.absent(),
|
this.mediaId = const Value.absent(),
|
||||||
|
this.downloadToken = const Value.absent(),
|
||||||
this.quotesMessageId = const Value.absent(),
|
this.quotesMessageId = const Value.absent(),
|
||||||
this.isDeletedFromSender = const Value.absent(),
|
this.isDeletedFromSender = const Value.absent(),
|
||||||
this.isEdited = const Value.absent(),
|
this.isEdited = const Value.absent(),
|
||||||
|
|
@ -2746,6 +2780,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
this.senderId = const Value.absent(),
|
this.senderId = const Value.absent(),
|
||||||
this.content = const Value.absent(),
|
this.content = const Value.absent(),
|
||||||
this.mediaId = const Value.absent(),
|
this.mediaId = const Value.absent(),
|
||||||
|
this.downloadToken = const Value.absent(),
|
||||||
this.quotesMessageId = const Value.absent(),
|
this.quotesMessageId = const Value.absent(),
|
||||||
this.isDeletedFromSender = const Value.absent(),
|
this.isDeletedFromSender = const Value.absent(),
|
||||||
this.isEdited = const Value.absent(),
|
this.isEdited = const Value.absent(),
|
||||||
|
|
@ -2763,6 +2798,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
Expression<int>? senderId,
|
Expression<int>? senderId,
|
||||||
Expression<String>? content,
|
Expression<String>? content,
|
||||||
Expression<String>? mediaId,
|
Expression<String>? mediaId,
|
||||||
|
Expression<Uint8List>? downloadToken,
|
||||||
Expression<String>? quotesMessageId,
|
Expression<String>? quotesMessageId,
|
||||||
Expression<bool>? isDeletedFromSender,
|
Expression<bool>? isDeletedFromSender,
|
||||||
Expression<bool>? isEdited,
|
Expression<bool>? isEdited,
|
||||||
|
|
@ -2780,6 +2816,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
if (senderId != null) 'sender_id': senderId,
|
if (senderId != null) 'sender_id': senderId,
|
||||||
if (content != null) 'content': content,
|
if (content != null) 'content': content,
|
||||||
if (mediaId != null) 'media_id': mediaId,
|
if (mediaId != null) 'media_id': mediaId,
|
||||||
|
if (downloadToken != null) 'download_token': downloadToken,
|
||||||
if (quotesMessageId != null) 'quotes_message_id': quotesMessageId,
|
if (quotesMessageId != null) 'quotes_message_id': quotesMessageId,
|
||||||
if (isDeletedFromSender != null)
|
if (isDeletedFromSender != null)
|
||||||
'is_deleted_from_sender': isDeletedFromSender,
|
'is_deleted_from_sender': isDeletedFromSender,
|
||||||
|
|
@ -2800,6 +2837,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
Value<int?>? senderId,
|
Value<int?>? senderId,
|
||||||
Value<String?>? content,
|
Value<String?>? content,
|
||||||
Value<String?>? mediaId,
|
Value<String?>? mediaId,
|
||||||
|
Value<Uint8List?>? downloadToken,
|
||||||
Value<String?>? quotesMessageId,
|
Value<String?>? quotesMessageId,
|
||||||
Value<bool>? isDeletedFromSender,
|
Value<bool>? isDeletedFromSender,
|
||||||
Value<bool>? isEdited,
|
Value<bool>? isEdited,
|
||||||
|
|
@ -2816,6 +2854,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
senderId: senderId ?? this.senderId,
|
senderId: senderId ?? this.senderId,
|
||||||
content: content ?? this.content,
|
content: content ?? this.content,
|
||||||
mediaId: mediaId ?? this.mediaId,
|
mediaId: mediaId ?? this.mediaId,
|
||||||
|
downloadToken: downloadToken ?? this.downloadToken,
|
||||||
quotesMessageId: quotesMessageId ?? this.quotesMessageId,
|
quotesMessageId: quotesMessageId ?? this.quotesMessageId,
|
||||||
isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender,
|
isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender,
|
||||||
isEdited: isEdited ?? this.isEdited,
|
isEdited: isEdited ?? this.isEdited,
|
||||||
|
|
@ -2847,6 +2886,9 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
if (mediaId.present) {
|
if (mediaId.present) {
|
||||||
map['media_id'] = Variable<String>(mediaId.value);
|
map['media_id'] = Variable<String>(mediaId.value);
|
||||||
}
|
}
|
||||||
|
if (downloadToken.present) {
|
||||||
|
map['download_token'] = Variable<Uint8List>(downloadToken.value);
|
||||||
|
}
|
||||||
if (quotesMessageId.present) {
|
if (quotesMessageId.present) {
|
||||||
map['quotes_message_id'] = Variable<String>(quotesMessageId.value);
|
map['quotes_message_id'] = Variable<String>(quotesMessageId.value);
|
||||||
}
|
}
|
||||||
|
|
@ -2888,6 +2930,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
||||||
..write('senderId: $senderId, ')
|
..write('senderId: $senderId, ')
|
||||||
..write('content: $content, ')
|
..write('content: $content, ')
|
||||||
..write('mediaId: $mediaId, ')
|
..write('mediaId: $mediaId, ')
|
||||||
|
..write('downloadToken: $downloadToken, ')
|
||||||
..write('quotesMessageId: $quotesMessageId, ')
|
..write('quotesMessageId: $quotesMessageId, ')
|
||||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||||
..write('isEdited: $isEdited, ')
|
..write('isEdited: $isEdited, ')
|
||||||
|
|
@ -7100,7 +7143,7 @@ typedef $$MediaFilesTableCreateCompanionBuilder = MediaFilesCompanion Function({
|
||||||
required MediaType type,
|
required MediaType type,
|
||||||
Value<UploadState?> uploadState,
|
Value<UploadState?> uploadState,
|
||||||
Value<DownloadState?> downloadState,
|
Value<DownloadState?> downloadState,
|
||||||
required bool requiresAuthentication,
|
Value<bool> requiresAuthentication,
|
||||||
Value<bool> reopenByContact,
|
Value<bool> reopenByContact,
|
||||||
Value<bool> stored,
|
Value<bool> stored,
|
||||||
Value<List<int>?> reuploadRequestedBy,
|
Value<List<int>?> reuploadRequestedBy,
|
||||||
|
|
@ -7433,7 +7476,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
||||||
required MediaType type,
|
required MediaType type,
|
||||||
Value<UploadState?> uploadState = const Value.absent(),
|
Value<UploadState?> uploadState = const Value.absent(),
|
||||||
Value<DownloadState?> downloadState = const Value.absent(),
|
Value<DownloadState?> downloadState = const Value.absent(),
|
||||||
required bool requiresAuthentication,
|
Value<bool> requiresAuthentication = const Value.absent(),
|
||||||
Value<bool> reopenByContact = const Value.absent(),
|
Value<bool> reopenByContact = const Value.absent(),
|
||||||
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(),
|
||||||
|
|
@ -7513,6 +7556,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
|
||||||
Value<int?> senderId,
|
Value<int?> senderId,
|
||||||
Value<String?> content,
|
Value<String?> content,
|
||||||
Value<String?> mediaId,
|
Value<String?> mediaId,
|
||||||
|
Value<Uint8List?> downloadToken,
|
||||||
Value<String?> quotesMessageId,
|
Value<String?> quotesMessageId,
|
||||||
Value<bool> isDeletedFromSender,
|
Value<bool> isDeletedFromSender,
|
||||||
Value<bool> isEdited,
|
Value<bool> isEdited,
|
||||||
|
|
@ -7530,6 +7574,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
|
||||||
Value<int?> senderId,
|
Value<int?> senderId,
|
||||||
Value<String?> content,
|
Value<String?> content,
|
||||||
Value<String?> mediaId,
|
Value<String?> mediaId,
|
||||||
|
Value<Uint8List?> downloadToken,
|
||||||
Value<String?> quotesMessageId,
|
Value<String?> quotesMessageId,
|
||||||
Value<bool> isDeletedFromSender,
|
Value<bool> isDeletedFromSender,
|
||||||
Value<bool> isEdited,
|
Value<bool> isEdited,
|
||||||
|
|
@ -7671,6 +7716,9 @@ class $$MessagesTableFilterComposer
|
||||||
ColumnFilters<String> get content => $composableBuilder(
|
ColumnFilters<String> get content => $composableBuilder(
|
||||||
column: $table.content, builder: (column) => ColumnFilters(column));
|
column: $table.content, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<Uint8List> get downloadToken => $composableBuilder(
|
||||||
|
column: $table.downloadToken, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<bool> get isDeletedFromSender => $composableBuilder(
|
ColumnFilters<bool> get isDeletedFromSender => $composableBuilder(
|
||||||
column: $table.isDeletedFromSender,
|
column: $table.isDeletedFromSender,
|
||||||
builder: (column) => ColumnFilters(column));
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
@ -7856,6 +7904,10 @@ class $$MessagesTableOrderingComposer
|
||||||
ColumnOrderings<String> get content => $composableBuilder(
|
ColumnOrderings<String> get content => $composableBuilder(
|
||||||
column: $table.content, builder: (column) => ColumnOrderings(column));
|
column: $table.content, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<Uint8List> get downloadToken => $composableBuilder(
|
||||||
|
column: $table.downloadToken,
|
||||||
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<bool> get isDeletedFromSender => $composableBuilder(
|
ColumnOrderings<bool> get isDeletedFromSender => $composableBuilder(
|
||||||
column: $table.isDeletedFromSender,
|
column: $table.isDeletedFromSender,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
@ -7978,6 +8030,9 @@ class $$MessagesTableAnnotationComposer
|
||||||
GeneratedColumn<String> get content =>
|
GeneratedColumn<String> get content =>
|
||||||
$composableBuilder(column: $table.content, builder: (column) => column);
|
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<Uint8List> get downloadToken => $composableBuilder(
|
||||||
|
column: $table.downloadToken, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<bool> get isDeletedFromSender => $composableBuilder(
|
GeneratedColumn<bool> get isDeletedFromSender => $composableBuilder(
|
||||||
column: $table.isDeletedFromSender, builder: (column) => column);
|
column: $table.isDeletedFromSender, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -8181,6 +8236,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
Value<int?> senderId = const Value.absent(),
|
Value<int?> senderId = const Value.absent(),
|
||||||
Value<String?> content = const Value.absent(),
|
Value<String?> content = const Value.absent(),
|
||||||
Value<String?> mediaId = const Value.absent(),
|
Value<String?> mediaId = const Value.absent(),
|
||||||
|
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||||
Value<String?> quotesMessageId = const Value.absent(),
|
Value<String?> quotesMessageId = const Value.absent(),
|
||||||
Value<bool> isDeletedFromSender = const Value.absent(),
|
Value<bool> isDeletedFromSender = const Value.absent(),
|
||||||
Value<bool> isEdited = const Value.absent(),
|
Value<bool> isEdited = const Value.absent(),
|
||||||
|
|
@ -8198,6 +8254,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
senderId: senderId,
|
senderId: senderId,
|
||||||
content: content,
|
content: content,
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
|
downloadToken: downloadToken,
|
||||||
quotesMessageId: quotesMessageId,
|
quotesMessageId: quotesMessageId,
|
||||||
isDeletedFromSender: isDeletedFromSender,
|
isDeletedFromSender: isDeletedFromSender,
|
||||||
isEdited: isEdited,
|
isEdited: isEdited,
|
||||||
|
|
@ -8215,6 +8272,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
Value<int?> senderId = const Value.absent(),
|
Value<int?> senderId = const Value.absent(),
|
||||||
Value<String?> content = const Value.absent(),
|
Value<String?> content = const Value.absent(),
|
||||||
Value<String?> mediaId = const Value.absent(),
|
Value<String?> mediaId = const Value.absent(),
|
||||||
|
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||||
Value<String?> quotesMessageId = const Value.absent(),
|
Value<String?> quotesMessageId = const Value.absent(),
|
||||||
Value<bool> isDeletedFromSender = const Value.absent(),
|
Value<bool> isDeletedFromSender = const Value.absent(),
|
||||||
Value<bool> isEdited = const Value.absent(),
|
Value<bool> isEdited = const Value.absent(),
|
||||||
|
|
@ -8232,6 +8290,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
senderId: senderId,
|
senderId: senderId,
|
||||||
content: content,
|
content: content,
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
|
downloadToken: downloadToken,
|
||||||
quotesMessageId: quotesMessageId,
|
quotesMessageId: quotesMessageId,
|
||||||
isDeletedFromSender: isDeletedFromSender,
|
isDeletedFromSender: isDeletedFromSender,
|
||||||
isEdited: isEdited,
|
isEdited: isEdited,
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/message_old.dart';
|
import 'package:twonly/src/model/json/message_old.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart' as send;
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||||
import 'package:twonly/src/services/thumbnail.service.dart';
|
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||||
|
|
||||||
class MemoryItem {
|
class MemoryItem {
|
||||||
MemoryItem({
|
MemoryItem({
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||||
as server;
|
as server;
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/server_messages.dart';
|
import 'package:twonly/src/services/api/server_messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/mediafile.service.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:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
Future<void> tryDownloadAllMediaFiles({bool force = false}) async {
|
Future<void> tryDownloadAllMediaFiles({bool force = false}) async {
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
|
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
Future<void> initFileDownloader() async {
|
||||||
|
FileDownloader().updates.listen((update) async {
|
||||||
|
switch (update) {
|
||||||
|
case TaskStatusUpdate():
|
||||||
|
if (update.task.taskId.contains('upload_')) {
|
||||||
|
await handleUploadStatusUpdate(update);
|
||||||
|
}
|
||||||
|
if (update.task.taskId.contains('download_')) {
|
||||||
|
await handleDownloadStatusUpdate(update);
|
||||||
|
}
|
||||||
|
if (update.task.taskId.contains('backup')) {
|
||||||
|
await handleBackupStatusUpdate(update);
|
||||||
|
}
|
||||||
|
case TaskProgressUpdate():
|
||||||
|
Log.info(
|
||||||
|
'Progress update for ${update.task} with progress ${update.progress}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await FileDownloader().start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var androidConfig = [];
|
||||||
|
if (kDebugMode) {
|
||||||
|
androidConfig = [(Config.bypassTLSCertificateValidation, kDebugMode)];
|
||||||
|
}
|
||||||
|
await FileDownloader().configure(androidConfig: androidConfig);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
FileDownloader().configureNotification(
|
||||||
|
running: const TaskNotification(
|
||||||
|
'Uploading/Downloading',
|
||||||
|
'{filename} ({progress}).',
|
||||||
|
),
|
||||||
|
progressBar: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
|
|
@ -17,14 +16,13 @@ import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/database/tables/media_uploads_table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/message_old.dart';
|
import 'package:twonly/src/model/json/message_old.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||||
|
|
@ -33,76 +31,6 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:video_compress/video_compress.dart';
|
import 'package:video_compress/video_compress.dart';
|
||||||
|
|
||||||
Future<ErrorCode?> isAllowedToSend() async {
|
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return null;
|
|
||||||
if (user.subscriptionPlan == 'Free') {
|
|
||||||
var todaysImageCounter = user.todaysImageCounter;
|
|
||||||
if (user.lastImageSend != null && user.todaysImageCounter != null) {
|
|
||||||
if (isToday(user.lastImageSend!)) {
|
|
||||||
if (user.todaysImageCounter == 10) {
|
|
||||||
return ErrorCode.PlanLimitReached;
|
|
||||||
}
|
|
||||||
todaysImageCounter = user.todaysImageCounter! + 1;
|
|
||||||
} else {
|
|
||||||
todaysImageCounter = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
todaysImageCounter = 1;
|
|
||||||
}
|
|
||||||
await updateUserdata((user) {
|
|
||||||
user
|
|
||||||
..lastImageSend = DateTime.now()
|
|
||||||
..todaysImageCounter = todaysImageCounter;
|
|
||||||
return user;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initFileDownloader() async {
|
|
||||||
FileDownloader().updates.listen((update) async {
|
|
||||||
switch (update) {
|
|
||||||
case TaskStatusUpdate():
|
|
||||||
if (update.task.taskId.contains('upload_')) {
|
|
||||||
await handleUploadStatusUpdate(update);
|
|
||||||
}
|
|
||||||
if (update.task.taskId.contains('download_')) {
|
|
||||||
await handleDownloadStatusUpdate(update);
|
|
||||||
}
|
|
||||||
if (update.task.taskId.contains('backup')) {
|
|
||||||
await handleBackupStatusUpdate(update);
|
|
||||||
}
|
|
||||||
case TaskProgressUpdate():
|
|
||||||
Log.info(
|
|
||||||
'Progress update for ${update.task} with progress ${update.progress}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await FileDownloader().start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
var androidConfig = [];
|
|
||||||
if (kDebugMode) {
|
|
||||||
androidConfig = [(Config.bypassTLSCertificateValidation, kDebugMode)];
|
|
||||||
}
|
|
||||||
await FileDownloader().configure(androidConfig: androidConfig);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kDebugMode) {
|
|
||||||
FileDownloader().configureNotification(
|
|
||||||
running: const TaskNotification(
|
|
||||||
'Uploading/Downloading',
|
|
||||||
'{filename} ({progress}).',
|
|
||||||
),
|
|
||||||
progressBar: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// States:
|
/// States:
|
||||||
/// when user recorded an video
|
/// when user recorded an video
|
||||||
/// 1. Compress video
|
/// 1. Compress video
|
||||||
|
|
@ -115,43 +43,43 @@ Future<void> initFileDownloader() async {
|
||||||
|
|
||||||
/// Create a new entry in the database
|
/// Create a new entry in the database
|
||||||
|
|
||||||
Future<bool> checkForFailedUploads() async {
|
// Future<bool> checkForFailedUploads() async {
|
||||||
final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload();
|
// final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload();
|
||||||
final mediaUploadIds = <int>[];
|
// final mediaUploadIds = <int>[];
|
||||||
for (final message in messages) {
|
// for (final message in messages) {
|
||||||
if (mediaUploadIds.contains(message.mediaUploadId)) {
|
// if (mediaUploadIds.contains(message.mediaUploadId)) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
final affectedRows = await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
// final affectedRows = await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||||
message.mediaUploadId!,
|
// message.mediaUploadId!,
|
||||||
const MediaUploadsCompanion(
|
// const MediaUploadsCompanion(
|
||||||
state: Value(UploadState.pending),
|
// state: Value(UploadState.pending),
|
||||||
encryptionData: Value(
|
// encryptionData: Value(
|
||||||
null, // start from scratch e.q. encrypt the files again if already happen
|
// null, // start from scratch e.q. encrypt the files again if already happen
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
if (affectedRows == 0) {
|
// if (affectedRows == 0) {
|
||||||
Log.error(
|
// Log.error(
|
||||||
'The media from message ${message.messageId} already deleted.',
|
// 'The media from message ${message.messageId} already deleted.',
|
||||||
);
|
// );
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
// await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
message.messageId,
|
// message.messageId,
|
||||||
const MessagesCompanion(
|
// const MessagesCompanion(
|
||||||
errorWhileSending: Value(true),
|
// errorWhileSending: Value(true),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
mediaUploadIds.add(message.mediaUploadId!);
|
// mediaUploadIds.add(message.mediaUploadId!);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (messages.isNotEmpty) {
|
// if (messages.isNotEmpty) {
|
||||||
Log.error(
|
// Log.error(
|
||||||
'Got ${messages.length} messages (${mediaUploadIds.length} media upload files) that are not correctly uploaded. Trying from scratch again.',
|
// 'Got ${messages.length} messages (${mediaUploadIds.length} media upload files) that are not correctly uploaded. Trying from scratch again.',
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
return mediaUploadIds.isNotEmpty; // return true if there are affected
|
// return mediaUploadIds.isNotEmpty; // return true if there are affected
|
||||||
}
|
// }
|
||||||
|
|
||||||
final lockingHandleMediaFile = Mutex();
|
final lockingHandleMediaFile = Mutex();
|
||||||
Future<void> retryMediaUpload(bool appRestarted, {int maxRetries = 3}) async {
|
Future<void> retryMediaUpload(bool appRestarted, {int maxRetries = 3}) async {
|
||||||
|
|
@ -192,82 +120,25 @@ Future<void> retryMediaUpload(bool appRestarted, {int maxRetries = 3}) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> initMediaUpload() async {
|
Future<MediaFileService?> initializeMediaUpload(
|
||||||
return twonlyDB.mediaUploadsDao
|
MediaType type,
|
||||||
.insertMediaUpload(const MediaUploadsCompanion());
|
int? displayLimitInMilliseconds,
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> addVideoToUpload(int mediaUploadId, File videoFilePath) async {
|
|
||||||
final basePath = await getMediaFilePath(mediaUploadId, 'send');
|
|
||||||
await videoFilePath.copy('$basePath.original.mp4');
|
|
||||||
return compressVideoIfExists(mediaUploadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uint8List> addOrModifyImageToUpload(
|
|
||||||
int mediaUploadId,
|
|
||||||
Uint8List imageBytes,
|
|
||||||
) async {
|
) async {
|
||||||
Uint8List imageBytesCompressed;
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
|
final encryptionKey = await (await chacha20.newSecretKey()).extract();
|
||||||
|
final encryptionNonce = chacha20.newNonce();
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||||
|
MediaFilesCompanion(
|
||||||
Log.info('Raw images size in bytes: ${imageBytes.length}');
|
uploadState: const Value(UploadState.initialized),
|
||||||
|
displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
|
||||||
try {
|
encryptionKey: Value(Uint8List.fromList(encryptionKey.bytes)),
|
||||||
imageBytesCompressed = await FlutterImageCompress.compressWithList(
|
encryptionNonce: Value(Uint8List.fromList(encryptionNonce)),
|
||||||
format: CompressFormat.webp,
|
type: Value(type),
|
||||||
// minHeight: 0,
|
|
||||||
// minWidth: 0,
|
|
||||||
imageBytes,
|
|
||||||
quality: 90,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (imageBytesCompressed.length >= 1 * 1000 * 1000) {
|
|
||||||
// if the media file is over 2MB compress it with 60%
|
|
||||||
imageBytesCompressed = await FlutterImageCompress.compressWithList(
|
|
||||||
format: CompressFormat.webp,
|
|
||||||
imageBytes,
|
|
||||||
quality: 60,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await writeSendMediaFile(mediaUploadId, 'png', imageBytesCompressed);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('$e');
|
|
||||||
// as a fall back use the original image
|
|
||||||
await writeSendMediaFile(mediaUploadId, 'png', imageBytes);
|
|
||||||
imageBytesCompressed = imageBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
stopwatch.stop();
|
|
||||||
|
|
||||||
Log.info(
|
|
||||||
'Compression the image took: ${stopwatch.elapsedMilliseconds} milliseconds',
|
|
||||||
);
|
|
||||||
Log.info('Raw images size in bytes: ${imageBytesCompressed.length}');
|
|
||||||
|
|
||||||
// stopwatch.reset();
|
|
||||||
// stopwatch.start();
|
|
||||||
|
|
||||||
// // var helper = MediaUploadHelper();
|
|
||||||
// try {
|
|
||||||
// final webpBytes =
|
|
||||||
// await convertAndCompressImage(pngRawImageBytes: imageBytes);
|
|
||||||
// Log.info(
|
|
||||||
// 'Compression the image in rust took: ${stopwatch.elapsedMilliseconds} milliseconds');
|
|
||||||
// Log.info("Raw images size in bytes using webp: ${webpBytes.length}");
|
|
||||||
// } catch (e) {
|
|
||||||
// Log.error("$e");
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// in case the media file was already encrypted of even uploaded
|
|
||||||
/// remove the data so it will be done again.
|
|
||||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
|
||||||
mediaUploadId,
|
|
||||||
const MediaUploadsCompanion(
|
|
||||||
encryptionData: Value(null),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return imageBytesCompressed;
|
if (mediaFile == null) return null;
|
||||||
|
return MediaFileService.fromMedia(mediaFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handlePreProcessingState(MediaUpload media) async {
|
Future<void> handlePreProcessingState(MediaUpload media) async {
|
||||||
|
|
@ -304,7 +175,6 @@ Future<void> encryptMediaFiles(
|
||||||
final state = MediaEncryptionData();
|
final state = MediaEncryptionData();
|
||||||
|
|
||||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
final secretKey = await (await chacha20.newSecretKey()).extract();
|
|
||||||
|
|
||||||
state
|
state
|
||||||
..encryptionKey = secretKey.bytes
|
..encryptionKey = secretKey.bytes
|
||||||
|
|
@ -338,70 +208,37 @@ Future<void> encryptMediaFiles(
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> finalizeUpload(
|
Future<void> finalizeUpload(
|
||||||
int mediaUploadId,
|
MediaFileService mediaService,
|
||||||
List<int> contactIds,
|
List<String> groupIds,
|
||||||
bool isRealTwonly,
|
|
||||||
bool isVideo,
|
|
||||||
bool mirrorVideo,
|
|
||||||
int maxShowTime,
|
|
||||||
) async {
|
) async {
|
||||||
final metadata = MediaUploadMetadata()
|
final messageIds = <Message>[];
|
||||||
..contactIds = contactIds
|
|
||||||
..isRealTwonly = isRealTwonly
|
|
||||||
..messageSendAt = DateTime.now()
|
|
||||||
..isVideo = isVideo
|
|
||||||
..maxShowTime = maxShowTime
|
|
||||||
..mirrorVideo = mirrorVideo;
|
|
||||||
|
|
||||||
final messageIds = <int>[];
|
for (final groupId in groupIds) {
|
||||||
|
final message = await twonlyDB.messagesDao.insertMessage(
|
||||||
for (final contactId in contactIds) {
|
|
||||||
final messageId = await twonlyDB.messagesDao.insertMessage(
|
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
contactId: Value(contactId),
|
groupId: Value(groupId),
|
||||||
kind: const Value(MessageKind.media),
|
mediaId: Value(mediaService.mediaFile.mediaId),
|
||||||
sendAt: Value(metadata.messageSendAt),
|
),
|
||||||
downloadState: const Value(DownloadState.pending),
|
);
|
||||||
mediaUploadId: Value(mediaUploadId),
|
if (message != null) {
|
||||||
contentJson: Value(
|
messageIds.add(message);
|
||||||
jsonEncode(
|
// de-archive contact when sending a new message
|
||||||
MediaMessageContent(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
maxShowTime: maxShowTime,
|
message.groupId,
|
||||||
isRealTwonly: isRealTwonly,
|
const GroupsCompanion(
|
||||||
isVideo: isVideo,
|
archived: Value(false),
|
||||||
mirrorVideo: mirrorVideo,
|
|
||||||
).toJson(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
// de-archive contact when sending a new message
|
|
||||||
await twonlyDB.contactsDao.updateContact(
|
|
||||||
contactId,
|
|
||||||
const ContactsCompanion(
|
|
||||||
archived: Value(false),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (messageId != null) {
|
|
||||||
messageIds.add(messageId);
|
|
||||||
} else {
|
} else {
|
||||||
Log.error('Error inserting media upload message in database.');
|
Log.error('Error inserting media upload message in database.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
unawaited(handleNextMediaUploadSteps(mediaService.mediaFile.mediaId));
|
||||||
mediaUploadId,
|
|
||||||
MediaUploadsCompanion(
|
|
||||||
messageIds: Value(messageIds),
|
|
||||||
metadata: Value(metadata),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
unawaited(handleNextMediaUploadSteps(mediaUploadId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final lockingHandleNextMediaUploadStep = Mutex();
|
final lockingHandleNextMediaUploadStep = Mutex();
|
||||||
Future<void> handleNextMediaUploadSteps(int mediaUploadId) async {
|
Future<void> handleNextMediaUploadSteps(String mediaUploadId) async {
|
||||||
await lockingHandleNextMediaUploadStep.protect(() async {
|
await lockingHandleNextMediaUploadStep.protect(() async {
|
||||||
final mediaUpload = await twonlyDB.mediaUploadsDao
|
final mediaUpload = await twonlyDB.mediaUploadsDao
|
||||||
.getMediaUploadById(mediaUploadId)
|
.getMediaUploadById(mediaUploadId)
|
||||||
|
|
@ -549,7 +386,7 @@ Future<void> handleMediaUpload(MediaUpload media) async {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final downloadToken = createDownloadToken();
|
final downloadToken = getRandomUint8List(32);
|
||||||
|
|
||||||
final msg = MessageJson(
|
final msg = MessageJson(
|
||||||
kind: MessageKind.media,
|
kind: MessageKind.media,
|
||||||
|
|
@ -734,159 +571,14 @@ Future<void> uploadFileFast(
|
||||||
Log.info('Upload successful!');
|
Log.info('Upload successful!');
|
||||||
await handleUploadSuccess(media);
|
await handleUploadSuccess(media);
|
||||||
return;
|
return;
|
||||||
|
} else if (response.statusCode == 429) {
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
media.mediaId,
|
||||||
|
const MediaFilesCompanion(
|
||||||
|
uploadState: Value(UploadState.uploadLimitReached),
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Log.info('Upload failed with status: ${response.statusCode}');
|
Log.info('Upload failed with status: ${response.statusCode}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> compressVideoIfExists(int mediaUploadId) async {
|
|
||||||
final basePath = await getMediaFilePath(mediaUploadId, 'send');
|
|
||||||
final videoOriginalFile = File('$basePath.original.mp4');
|
|
||||||
final videoCompressedFile = File('$basePath.mp4');
|
|
||||||
|
|
||||||
if (videoCompressedFile.existsSync()) {
|
|
||||||
// file is already compressed and exists
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!videoOriginalFile.existsSync()) {
|
|
||||||
// media upload does not have a video
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final stopwatch = Stopwatch()..start();
|
|
||||||
|
|
||||||
MediaInfo? mediaInfo;
|
|
||||||
try {
|
|
||||||
mediaInfo = await VideoCompress.compressVideo(
|
|
||||||
videoOriginalFile.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(
|
|
||||||
videoOriginalFile.path,
|
|
||||||
quality: VideoQuality.Res960x540Quality,
|
|
||||||
includeAudio: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('during video compression: $e');
|
|
||||||
}
|
|
||||||
stopwatch.stop();
|
|
||||||
Log.info('It took ${stopwatch.elapsedMilliseconds}ms to compress the video');
|
|
||||||
|
|
||||||
if (mediaInfo == null) {
|
|
||||||
Log.error('could not compress video.');
|
|
||||||
// as a fall back use the non compressed version
|
|
||||||
await videoOriginalFile.copy(videoCompressedFile.path);
|
|
||||||
await videoOriginalFile.delete();
|
|
||||||
} else {
|
|
||||||
await mediaInfo.file!.copy(videoCompressedFile.path);
|
|
||||||
await mediaInfo.file!.delete();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// --- helper functions ---
|
|
||||||
|
|
||||||
Future<Uint8List> readSendMediaFile(int mediaUploadId, String type) async {
|
|
||||||
final basePath = await getMediaFilePath(mediaUploadId, 'send');
|
|
||||||
final file = File('$basePath.$type');
|
|
||||||
if (!file.existsSync()) {
|
|
||||||
throw Exception('$file not found');
|
|
||||||
}
|
|
||||||
return file.readAsBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<File> writeSendMediaFile(
|
|
||||||
int mediaUploadId,
|
|
||||||
String type,
|
|
||||||
Uint8List data,
|
|
||||||
) async {
|
|
||||||
final basePath = await getMediaFilePath(mediaUploadId, 'send');
|
|
||||||
final file = File('$basePath.$type');
|
|
||||||
await file.writeAsBytes(data);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteSendMediaFile(int mediaUploadId, String type) async {
|
|
||||||
final basePath = await getMediaFilePath(mediaUploadId, 'send');
|
|
||||||
final file = File('$basePath.$type');
|
|
||||||
if (file.existsSync()) {
|
|
||||||
await file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getMediaFilePath(dynamic mediaId, String type) async {
|
|
||||||
final basedir = await getApplicationSupportDirectory();
|
|
||||||
final mediaSendDir = Directory(join(basedir.path, 'media', type));
|
|
||||||
if (!mediaSendDir.existsSync()) {
|
|
||||||
await mediaSendDir.create(recursive: true);
|
|
||||||
}
|
|
||||||
return join(mediaSendDir.path, '$mediaId');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getMediaBaseFilePath(String type) async {
|
|
||||||
final basedir = await getApplicationSupportDirectory();
|
|
||||||
final mediaSendDir = Directory(join(basedir.path, 'media', type));
|
|
||||||
if (!mediaSendDir.existsSync()) {
|
|
||||||
await mediaSendDir.create(recursive: true);
|
|
||||||
}
|
|
||||||
return mediaSendDir.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// combines two utf8 list
|
|
||||||
Uint8List combineUint8Lists(Uint8List list1, Uint8List list2) {
|
|
||||||
final combinedLength = 4 + list1.length + list2.length;
|
|
||||||
final combinedList = Uint8List(combinedLength);
|
|
||||||
ByteData.sublistView(combinedList).setInt32(0, list1.length);
|
|
||||||
combinedList
|
|
||||||
..setRange(4, 4 + list1.length, list1)
|
|
||||||
..setRange(4 + list1.length, combinedLength, list2);
|
|
||||||
return combinedList;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Uint8List> extractUint8Lists(Uint8List combinedList) {
|
|
||||||
final byteData = ByteData.sublistView(combinedList);
|
|
||||||
final sizeOfList1 = byteData.getInt32(0);
|
|
||||||
final list1 = Uint8List.view(combinedList.buffer, 4, sizeOfList1);
|
|
||||||
final list2 = Uint8List.view(
|
|
||||||
combinedList.buffer,
|
|
||||||
4 + sizeOfList1,
|
|
||||||
combinedList.lengthInBytes - 4 - sizeOfList1,
|
|
||||||
);
|
|
||||||
return [list1, list2];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> purgeSendMediaFiles() async {
|
|
||||||
final basedir = await getApplicationSupportDirectory();
|
|
||||||
final directory = Directory(join(basedir.path, 'media', 'send'));
|
|
||||||
await purgeMediaFiles(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
String uint8ListToHex(List<int> bytes) {
|
|
||||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List hexToUint8List(String hex) => Uint8List.fromList(
|
|
||||||
List<int>.generate(
|
|
||||||
hex.length ~/ 2,
|
|
||||||
(i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Uint8List createDownloadToken() {
|
|
||||||
final random = Random();
|
|
||||||
|
|
||||||
final token = Uint8List(32);
|
|
||||||
for (var j = 0; j < 32; j++) {
|
|
||||||
token[j] = random.nextInt(256); // Generate a random byte (0-255)
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
@ -4,9 +4,9 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/services/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
Future<void> handleMedia(
|
Future<void> handleMedia(
|
||||||
|
|
@ -157,7 +157,7 @@ Future<void> handleMediaUpdate(
|
||||||
await twonlyDB.mediaFilesDao.updateMedia(
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
mediaFile.mediaId,
|
mediaFile.mediaId,
|
||||||
MediaFilesCompanion(
|
MediaFilesCompanion(
|
||||||
uploadState: const Value(UploadState.pending),
|
uploadState: const Value(UploadState.uploading),
|
||||||
reuploadRequestedBy: Value(reuploadRequestedBy),
|
reuploadRequestedBy: Value(reuploadRequestedBy),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,7 @@ ClientToServer createClientToServerFromApplicationData(
|
||||||
return ClientToServer()..v0 = v0;
|
return ClientToServer()..v0 = v0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteContact(int contactId) async {
|
Future<void> rejectAndDeleteContact(int contactId) async {
|
||||||
await twonlyDB.signalDao.deleteAllByContactId(contactId);
|
|
||||||
await deleteSessionWithTarget(contactId);
|
|
||||||
await twonlyDB.contactsDao.deleteContactByUserId(contactId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> rejectUser(int contactId) async {
|
|
||||||
await sendCipherText(
|
await sendCipherText(
|
||||||
contactId,
|
contactId,
|
||||||
EncryptedContent(
|
EncryptedContent(
|
||||||
|
|
@ -72,6 +66,9 @@ Future<void> rejectUser(int contactId) async {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await twonlyDB.signalDao.deleteAllByContactId(contactId);
|
||||||
|
await deleteSessionWithTarget(contactId);
|
||||||
|
await twonlyDB.contactsDao.deleteContactByUserId(contactId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleMediaError(MediaFile media) async {
|
Future<void> handleMediaError(MediaFile media) async {
|
||||||
|
|
|
||||||
94
lib/src/services/mediafiles/compression.service.dart
Normal file
94
lib/src/services/mediafiles/compression.service.dart
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:video_compress/video_compress.dart';
|
||||||
|
|
||||||
|
Future<void> compressImage(
|
||||||
|
File sourceFile,
|
||||||
|
File destinationFile,
|
||||||
|
) async {
|
||||||
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var compressedBytes = await FlutterImageCompress.compressWithFile(
|
||||||
|
sourceFile.path,
|
||||||
|
format: CompressFormat.webp,
|
||||||
|
quality: 90,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (compressedBytes == null) {
|
||||||
|
throw Exception(
|
||||||
|
'Could not compress media file: $sourceFile. Sending original file.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.info('Compressed images size in bytes: ${compressedBytes.length}');
|
||||||
|
|
||||||
|
if (compressedBytes.length >= 1 * 1000 * 1000) {
|
||||||
|
// if the media file is over 1MB compress it with 60%
|
||||||
|
final tmpCompressedBytes = await FlutterImageCompress.compressWithFile(
|
||||||
|
sourceFile.path,
|
||||||
|
format: CompressFormat.webp,
|
||||||
|
quality: 60,
|
||||||
|
);
|
||||||
|
if (tmpCompressedBytes != null) {
|
||||||
|
Log.error(
|
||||||
|
'Could not compress media file with 60%: $sourceFile. Sending original 90% compressed file.',
|
||||||
|
);
|
||||||
|
compressedBytes = tmpCompressedBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await destinationFile.writeAsBytes(compressedBytes);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('$e');
|
||||||
|
sourceFile.copySync(destinationFile.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopwatch.stop();
|
||||||
|
|
||||||
|
Log.info(
|
||||||
|
'Compression of the image took: ${stopwatch.elapsedMilliseconds} milliseconds.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> compressVideo(
|
||||||
|
File sourceFile,
|
||||||
|
File destinationFile,
|
||||||
|
) async {
|
||||||
|
final stopwatch = Stopwatch()..start();
|
||||||
|
|
||||||
|
MediaInfo? mediaInfo;
|
||||||
|
try {
|
||||||
|
mediaInfo = await VideoCompress.compressVideo(
|
||||||
|
sourceFile.path,
|
||||||
|
quality: VideoQuality.Res1280x720Quality,
|
||||||
|
includeAudio:
|
||||||
|
true, // https://github.com/jonataslaw/VideoCompress/issues/184
|
||||||
|
);
|
||||||
|
|
||||||
|
Log.info('Video has now size of ${mediaInfo!.filesize} bytes.');
|
||||||
|
|
||||||
|
if (mediaInfo.filesize! >= 30 * 1000 * 1000) {
|
||||||
|
// if the media file is over 20MB compress it with low quality
|
||||||
|
mediaInfo = await VideoCompress.compressVideo(
|
||||||
|
sourceFile.path,
|
||||||
|
quality: VideoQuality.Res960x540Quality,
|
||||||
|
includeAudio: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('during video compression: $e');
|
||||||
|
}
|
||||||
|
stopwatch.stop();
|
||||||
|
Log.info('It took ${stopwatch.elapsedMilliseconds}ms to compress the video');
|
||||||
|
|
||||||
|
if (mediaInfo == null) {
|
||||||
|
Log.error('Could not compress video using original video.');
|
||||||
|
// as a fall back use the non compressed version
|
||||||
|
sourceFile.copySync(destinationFile.path);
|
||||||
|
} else {
|
||||||
|
await mediaInfo.file!.copy(destinationFile.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/thumbnail.service.dart';
|
import 'package:twonly/src/services/mediafiles/compression.service.dart';
|
||||||
|
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class MediaFileService {
|
class MediaFileService {
|
||||||
|
|
@ -39,6 +39,28 @@ class MediaFileService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setDisplayLimit(int? displayLimitInMilliseconds) async {
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(
|
||||||
|
displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await updateFromDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setRequiresAuth(bool requiresAuthentication) async {
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(
|
||||||
|
requiresAuthentication: Value(requiresAuthentication),
|
||||||
|
displayLimitInMilliseconds:
|
||||||
|
requiresAuthentication ? const Value(12) : const Value.absent(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await updateFromDB();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> createThumbnail() async {
|
Future<void> createThumbnail() async {
|
||||||
if (!storedPath.existsSync()) {
|
if (!storedPath.existsSync()) {
|
||||||
Log.error('Could not create Thumbnail as stored media does not exists.');
|
Log.error('Could not create Thumbnail as stored media does not exists.');
|
||||||
|
|
@ -54,18 +76,35 @@ class MediaFileService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> compressMedia() async {
|
||||||
|
if (!originalPath.existsSync()) {
|
||||||
|
Log.error('Could not compress as original media does not exists.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (mediaFile.type) {
|
||||||
|
case MediaType.image:
|
||||||
|
await compressImage(originalPath, tempPath);
|
||||||
|
case MediaType.video:
|
||||||
|
await compressVideo(originalPath, tempPath);
|
||||||
|
case MediaType.gif:
|
||||||
|
originalPath.renameSync(tempPath.path);
|
||||||
|
Log.error('Compression for .gif is not implemented yet.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void fullMediaRemoval() {
|
void fullMediaRemoval() {
|
||||||
if (tempPath.existsSync()) {
|
final pathsToRemove = [
|
||||||
tempPath.deleteSync();
|
tempPath,
|
||||||
}
|
encryptedPath,
|
||||||
if (encryptedPath.existsSync()) {
|
originalPath,
|
||||||
encryptedPath.deleteSync();
|
storedPath,
|
||||||
}
|
thumbnailPath
|
||||||
if (storedPath.existsSync()) {
|
];
|
||||||
storedPath.deleteSync();
|
|
||||||
}
|
for (final path in pathsToRemove) {
|
||||||
if (thumbnailPath.existsSync()) {
|
if (path.existsSync()) {
|
||||||
thumbnailPath.deleteSync();
|
path.deleteSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,4 +160,8 @@ class MediaFileService {
|
||||||
'tmp',
|
'tmp',
|
||||||
namePrefix: '.encrypted',
|
namePrefix: '.encrypted',
|
||||||
);
|
);
|
||||||
|
File get originalPath => _buildFilePath(
|
||||||
|
'tmp',
|
||||||
|
namePrefix: '.original',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/hashlib.dart';
|
import 'package:hashlib/hashlib.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/backup.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/backup.pb.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -11,14 +9,13 @@ import 'package:local_auth/local_auth.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/model/json/message_old.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
extension ShortCutsExtension on BuildContext {
|
extension ShortCutsExtension on BuildContext {
|
||||||
AppLocalizations get lang => AppLocalizations.of(this)!;
|
AppLocalizations get lang => AppLocalizations.of(this)!;
|
||||||
TwonlyDatabase get db => Provider.of<TwonlyDatabase>(this);
|
TwonlyDB get db => Provider.of<TwonlyDB>(this);
|
||||||
ColorScheme get color => Theme.of(this).colorScheme;
|
ColorScheme get color => Theme.of(this).colorScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,31 +242,19 @@ String formatBytes(int bytes, {int decimalPlaces = 2}) {
|
||||||
return '${formattedSize.toStringAsFixed(decimalPlaces)} ${units[unitIndex]}';
|
return '${formattedSize.toStringAsFixed(decimalPlaces)} ${units[unitIndex]}';
|
||||||
}
|
}
|
||||||
|
|
||||||
String getMessageText(Message message) {
|
|
||||||
try {
|
|
||||||
if (message.contentJson == null) return '';
|
|
||||||
return TextMessageContent.fromJson(jsonDecode(message.contentJson!) as Map)
|
|
||||||
.text;
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaMessageContent? getMediaContent(Message message) {
|
|
||||||
try {
|
|
||||||
if (message.contentJson == null) return null;
|
|
||||||
return MediaMessageContent.fromJson(
|
|
||||||
jsonDecode(message.contentJson!) as Map,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isUUIDNewer(String uuid1, String uuid2) {
|
bool isUUIDNewer(String uuid1, String uuid2) {
|
||||||
final timestamp1 = int.parse(uuid1.substring(0, 8), radix: 16);
|
final timestamp1 = int.parse(uuid1.substring(0, 8), radix: 16);
|
||||||
final timestamp2 = int.parse(uuid2.substring(0, 8), radix: 16);
|
final timestamp2 = int.parse(uuid2.substring(0, 8), radix: 16);
|
||||||
return timestamp1 > timestamp2;
|
return timestamp1 > timestamp2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String uint8ListToHex(List<int> bytes) {
|
||||||
|
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List hexToUint8List(String hex) => Uint8List.fromList(
|
||||||
|
List<int>.generate(
|
||||||
|
hex.length ~/ 2,
|
||||||
|
(i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
|
|
@ -14,6 +15,7 @@ Future<bool> isUserCreated() async {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
gUser = user;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +58,8 @@ Future<UserData?> updateUserdata(
|
||||||
final updated = updateUser(user);
|
final updated = updateUser(user);
|
||||||
await const FlutterSecureStorage()
|
await const FlutterSecureStorage()
|
||||||
.write(key: SecureStorageKeys.userData, value: jsonEncode(updated));
|
.write(key: SecureStorageKeys.userData, value: jsonEncode(updated));
|
||||||
return user;
|
gUser = updated;
|
||||||
|
return updated;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/thumbnail.service.dart';
|
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.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/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/tables/mediafiles.table.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/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
@ -299,17 +301,35 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
File? videoFilePath, {
|
File? videoFilePath, {
|
||||||
bool sharedFromGallery = false,
|
bool sharedFromGallery = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
final mediaFileService = await initializeMediaUpload(
|
||||||
|
(videoFilePath != null) ? MediaType.video : MediaType.image,
|
||||||
|
gUser.defaultShowTime,
|
||||||
|
);
|
||||||
|
if (!mounted) return true;
|
||||||
|
|
||||||
|
if (mediaFileService == null) {
|
||||||
|
Log.error('Could not generate media file service');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoFilePath != null) {
|
||||||
|
videoFilePath
|
||||||
|
..copySync(mediaFileService.originalPath.path)
|
||||||
|
..deleteSync();
|
||||||
|
|
||||||
|
// Start with compressing the video, to speed up the process in case the video is not changed.
|
||||||
|
unawaited(mediaFileService.compressMedia());
|
||||||
|
}
|
||||||
|
|
||||||
final shouldReturn = await Navigator.push(
|
final shouldReturn = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
||||||
videoFilePath: videoFilePath,
|
imageBytesFuture: imageBytes,
|
||||||
imageBytes: imageBytes,
|
|
||||||
sharedFromGallery: sharedFromGallery,
|
sharedFromGallery: sharedFromGallery,
|
||||||
sendTo: widget.sendTo,
|
sendTo: widget.sendTo,
|
||||||
mirrorVideo: isFront && Platform.isAndroid && false,
|
mediaFileService: mediaFileService,
|
||||||
useHighQuality: true,
|
|
||||||
),
|
),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
return child;
|
return child;
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,19 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.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:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
@ -34,32 +39,26 @@ const gMediaShowInfinite = 999999;
|
||||||
|
|
||||||
class ShareImageEditorView extends StatefulWidget {
|
class ShareImageEditorView extends StatefulWidget {
|
||||||
const ShareImageEditorView({
|
const ShareImageEditorView({
|
||||||
required this.mirrorVideo,
|
|
||||||
required this.useHighQuality,
|
|
||||||
required this.sharedFromGallery,
|
required this.sharedFromGallery,
|
||||||
|
required this.mediaFileService,
|
||||||
super.key,
|
super.key,
|
||||||
this.imageBytes,
|
this.imageBytesFuture,
|
||||||
this.sendTo,
|
this.sendTo,
|
||||||
this.videoFilePath,
|
|
||||||
});
|
});
|
||||||
final Future<Uint8List?>? imageBytes;
|
final Future<Uint8List?>? imageBytesFuture;
|
||||||
final File? videoFilePath;
|
final Group? sendTo;
|
||||||
final Contact? sendTo;
|
|
||||||
final bool mirrorVideo;
|
|
||||||
final bool useHighQuality;
|
|
||||||
final bool sharedFromGallery;
|
final bool sharedFromGallery;
|
||||||
|
final MediaFileService mediaFileService;
|
||||||
@override
|
@override
|
||||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShareImageEditorView extends State<ShareImageEditorView> {
|
class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
bool _isRealTwonly = false;
|
|
||||||
int maxShowTime = gMediaShowInfinite;
|
|
||||||
double tabDownPosition = 0;
|
double tabDownPosition = 0;
|
||||||
bool sendingOrLoadingImage = true;
|
bool sendingOrLoadingImage = true;
|
||||||
bool loadingImage = true;
|
bool loadingImage = true;
|
||||||
bool isDisposed = false;
|
bool isDisposed = false;
|
||||||
HashSet<int> selectedUserIds = HashSet();
|
HashSet<String> selectedGroupIds = HashSet();
|
||||||
double widthRatio = 1;
|
double widthRatio = 1;
|
||||||
double heightRatio = 1;
|
double heightRatio = 1;
|
||||||
double pixelRatio = 1;
|
double pixelRatio = 1;
|
||||||
|
|
@ -68,26 +67,31 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
||||||
/// Media upload variables
|
/// Media upload variables
|
||||||
int? mediaUploadId;
|
|
||||||
Future<bool>? videoUploadHandler;
|
Future<bool>? videoUploadHandler;
|
||||||
|
|
||||||
|
MediaFileService get mediaService => widget.mediaFileService;
|
||||||
|
MediaFile get media => widget.mediaFileService.mediaFile;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
unawaited(initAsync());
|
|
||||||
unawaited(initMediaFileUpload());
|
|
||||||
layers.add(FilterLayerData());
|
layers.add(FilterLayerData());
|
||||||
|
|
||||||
if (widget.sendTo != null) {
|
if (widget.sendTo != null) {
|
||||||
selectedUserIds.add(widget.sendTo!.userId);
|
selectedGroupIds.add(widget.sendTo!.groupId);
|
||||||
}
|
}
|
||||||
if (widget.imageBytes != null) {
|
|
||||||
unawaited(loadImage(widget.imageBytes!));
|
if (widget.imageBytesFuture != null) {
|
||||||
} else if (widget.videoFilePath != null) {
|
unawaited(loadImage(widget.imageBytesFuture!));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media.type == MediaType.video) {
|
||||||
setState(() {
|
setState(() {
|
||||||
sendingOrLoadingImage = false;
|
sendingOrLoadingImage = false;
|
||||||
loadingImage = false;
|
loadingImage = false;
|
||||||
});
|
});
|
||||||
videoController = VideoPlayerController.file(widget.videoFilePath!);
|
videoController = VideoPlayerController.file(mediaService.originalPath);
|
||||||
videoController?.setLooping(true);
|
videoController?.setLooping(true);
|
||||||
videoController?.initialize().then((_) async {
|
videoController?.initialize().then((_) async {
|
||||||
await videoController!.play();
|
await videoController!.play();
|
||||||
|
|
@ -97,29 +101,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return;
|
|
||||||
if (user.defaultShowTime != null) {
|
|
||||||
setState(() {
|
|
||||||
maxShowTime = user.defaultShowTime!;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initMediaFileUpload() async {
|
|
||||||
// media init was already called...
|
|
||||||
if (mediaUploadId != null) return;
|
|
||||||
|
|
||||||
mediaUploadId = await initMediaUpload();
|
|
||||||
|
|
||||||
if (widget.videoFilePath != null && mediaUploadId != null) {
|
|
||||||
// start with the video compression...
|
|
||||||
videoUploadHandler =
|
|
||||||
addVideoToUpload(mediaUploadId!, widget.videoFilePath!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
isDisposed = true;
|
isDisposed = true;
|
||||||
|
|
@ -128,14 +109,14 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateStatus(int userId, bool checked) {
|
void updateSelectedGroupIds(String groupId, bool checked) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
if (_isRealTwonly) {
|
if (media.requiresAuthentication) {
|
||||||
selectedUserIds.clear();
|
selectedGroupIds.clear();
|
||||||
}
|
}
|
||||||
selectedUserIds.add(userId);
|
selectedGroupIds.add(groupId);
|
||||||
} else {
|
} else {
|
||||||
selectedUserIds.remove(userId);
|
selectedGroupIds.remove(groupId);
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
@ -195,38 +176,36 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
NotificationBadge(
|
NotificationBadge(
|
||||||
count: (widget.videoFilePath != null)
|
count: (media.type != MediaType.video)
|
||||||
? '0'
|
? '0'
|
||||||
: maxShowTime == gMediaShowInfinite
|
: media.displayLimitInMilliseconds == null
|
||||||
? '∞'
|
? '∞'
|
||||||
: maxShowTime.toString(),
|
: media.displayLimitInMilliseconds.toString(),
|
||||||
child: ActionButton(
|
child: ActionButton(
|
||||||
(widget.videoFilePath != null)
|
(media.type != MediaType.video)
|
||||||
? maxShowTime == gMediaShowInfinite
|
? media.displayLimitInMilliseconds == null
|
||||||
? Icons.repeat_rounded
|
? Icons.repeat_rounded
|
||||||
: Icons.repeat_one_rounded
|
: Icons.repeat_one_rounded
|
||||||
: Icons.timer_outlined,
|
: Icons.timer_outlined,
|
||||||
tooltipText: context.lang.protectAsARealTwonly,
|
tooltipText: context.lang.protectAsARealTwonly,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (widget.videoFilePath != null) {
|
if (media.type != MediaType.video) {
|
||||||
setState(() {
|
await mediaService.setDisplayLimit(
|
||||||
if (maxShowTime == gMediaShowInfinite) {
|
(media.displayLimitInMilliseconds == null) ? 0 : null);
|
||||||
maxShowTime = 0;
|
if (!mounted) return;
|
||||||
} else {
|
setState(() {});
|
||||||
maxShowTime = gMediaShowInfinite;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (maxShowTime == gMediaShowInfinite) {
|
int? maxShowTime;
|
||||||
|
if (media.displayLimitInMilliseconds == null) {
|
||||||
maxShowTime = 1;
|
maxShowTime = 1;
|
||||||
} else if (maxShowTime == 1) {
|
} else if (media.displayLimitInMilliseconds == 1) {
|
||||||
maxShowTime = 5;
|
maxShowTime = 5;
|
||||||
} else if (maxShowTime == 5) {
|
} else if (media.displayLimitInMilliseconds == 5) {
|
||||||
maxShowTime = 20;
|
maxShowTime = 20;
|
||||||
} else {
|
|
||||||
maxShowTime = gMediaShowInfinite;
|
|
||||||
}
|
}
|
||||||
|
await mediaService.setDisplayLimit(maxShowTime);
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.defaultShowTime = maxShowTime;
|
user.defaultShowTime = maxShowTime;
|
||||||
|
|
@ -239,15 +218,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
ActionButton(
|
ActionButton(
|
||||||
FontAwesomeIcons.shieldHeart,
|
FontAwesomeIcons.shieldHeart,
|
||||||
tooltipText: context.lang.protectAsARealTwonly,
|
tooltipText: context.lang.protectAsARealTwonly,
|
||||||
color: _isRealTwonly
|
color: media.requiresAuthentication
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Colors.white,
|
: Colors.white,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
_isRealTwonly = !_isRealTwonly;
|
await mediaService.setRequiresAuth(!media.requiresAuthentication);
|
||||||
if (_isRealTwonly) {
|
selectedGroupIds = HashSet();
|
||||||
maxShowTime = 12;
|
|
||||||
}
|
|
||||||
selectedUserIds = HashSet();
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -308,11 +284,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pushShareImageView() async {
|
Future<void> pushShareImageView() async {
|
||||||
if (mediaUploadId == null) {
|
final imageBytes = storeImageAsOriginal();
|
||||||
await initMediaFileUpload();
|
|
||||||
if (mediaUploadId == null) return;
|
|
||||||
}
|
|
||||||
final imageBytes = getMergedImage();
|
|
||||||
await videoController?.pause();
|
await videoController?.pause();
|
||||||
if (isDisposed || !mounted) return;
|
if (isDisposed || !mounted) return;
|
||||||
final wasSend = await Navigator.push(
|
final wasSend = await Navigator.push(
|
||||||
|
|
@ -320,13 +292,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ShareImageView(
|
builder: (context) => ShareImageView(
|
||||||
imageBytesFuture: imageBytes,
|
imageBytesFuture: imageBytes,
|
||||||
isRealTwonly: _isRealTwonly,
|
selectedUserIds: selectedGroupIds,
|
||||||
maxShowTime: maxShowTime,
|
updateStatus: updateSelectedGroupIds,
|
||||||
selectedUserIds: selectedUserIds,
|
|
||||||
updateStatus: updateStatus,
|
|
||||||
videoUploadHandler: videoUploadHandler,
|
videoUploadHandler: videoUploadHandler,
|
||||||
mediaUploadId: mediaUploadId!,
|
mediaFileService: mediaService,
|
||||||
mirrorVideo: widget.mirrorVideo,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
) as bool?;
|
) as bool?;
|
||||||
|
|
@ -337,36 +306,46 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> getMergedImage() async {
|
Future<void> storeImageAsOriginal() async {
|
||||||
Uint8List? image;
|
if (layers.length == 1) {
|
||||||
|
if (layers.first is BackgroundLayerData) {
|
||||||
|
final image = (layers.first as BackgroundLayerData).image.bytes;
|
||||||
|
mediaService.originalPath.writeAsBytesSync(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layers.length > 1 || media.type != MediaType.video) {
|
||||||
TODO: When changed then create a new mediaID!!!!!!
|
|
||||||
As storedMediaId would overwrite it....
|
|
||||||
|
|
||||||
if (layers.length > 1 || widget.videoFilePath != null) {
|
|
||||||
for (final x in layers) {
|
for (final x in layers) {
|
||||||
x.showCustomButtons = false;
|
x.showCustomButtons = false;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
image = await screenshotController.capture(
|
final image = await screenshotController.capture(
|
||||||
pixelRatio: (widget.useHighQuality) ? pixelRatio : 1,
|
pixelRatio: pixelRatio,
|
||||||
);
|
);
|
||||||
|
if (image == null) {
|
||||||
|
Log.error('screenshotController did not return image bytes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaService.originalPath.writeAsBytesSync(image);
|
||||||
|
|
||||||
|
// In case the image was already stored, then rename the stored image.
|
||||||
|
|
||||||
|
if (mediaService.storedPath.existsSync()) {
|
||||||
|
final newPath = mediaService.storedPath.absolute.path
|
||||||
|
.replaceFirst(media.mediaId, uuid.v7());
|
||||||
|
mediaService.storedPath.renameSync(newPath);
|
||||||
|
}
|
||||||
|
|
||||||
for (final x in layers) {
|
for (final x in layers) {
|
||||||
x.showCustomButtons = true;
|
x.showCustomButtons = true;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
} else if (layers.length == 1) {
|
|
||||||
if (layers.first is BackgroundLayerData) {
|
|
||||||
image = (layers.first as BackgroundLayerData).image.bytes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return image;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadImage(Future<Uint8List?> imageFile) async {
|
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
|
||||||
final imageBytes = await imageFile;
|
await currentImage.load(await imageBytesFuture);
|
||||||
await currentImage.load(imageBytes);
|
|
||||||
if (isDisposed) return;
|
if (isDisposed) return;
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
@ -388,56 +367,29 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
setState(() {
|
setState(() {
|
||||||
sendingOrLoadingImage = true;
|
sendingOrLoadingImage = true;
|
||||||
});
|
});
|
||||||
final imageBytes = await getMergedImage();
|
|
||||||
|
if (media.type == MediaType.image) {
|
||||||
|
await storeImageAsOriginal();
|
||||||
|
}
|
||||||
|
if (media.type == MediaType.video) {
|
||||||
|
Log.error('TODO: COMBINE VIDEO AND IMAGE!!!');
|
||||||
|
}
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (imageBytes == null) {
|
|
||||||
|
// first finalize the upload
|
||||||
|
await finalizeUpload(mediaService, [widget.sendTo!.groupId]);
|
||||||
|
|
||||||
|
/// then call the upload process in the background
|
||||||
|
await encryptMediaFiles(
|
||||||
|
mediaUploadId!,
|
||||||
|
imageHandler,
|
||||||
|
videoUploadHandler,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
return;
|
|
||||||
}
|
|
||||||
final err = await isAllowedToSend();
|
|
||||||
if (!context.mounted) return;
|
|
||||||
|
|
||||||
if (err != null) {
|
|
||||||
setState(() {
|
|
||||||
sendingOrLoadingImage = false;
|
|
||||||
});
|
|
||||||
if (mounted) {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return SubscriptionView(
|
|
||||||
redirectError: err,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final imageHandler = addOrModifyImageToUpload(mediaUploadId!, imageBytes);
|
|
||||||
|
|
||||||
// first finalize the upload
|
|
||||||
await finalizeUpload(
|
|
||||||
mediaUploadId!,
|
|
||||||
[widget.sendTo!.userId],
|
|
||||||
_isRealTwonly,
|
|
||||||
widget.videoFilePath != null,
|
|
||||||
widget.mirrorVideo,
|
|
||||||
maxShowTime,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// then call the upload process in the background
|
|
||||||
await encryptMediaFiles(
|
|
||||||
mediaUploadId!,
|
|
||||||
imageHandler,
|
|
||||||
videoUploadHandler,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (context.mounted) {
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -543,10 +495,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
children: [
|
children: [
|
||||||
if (videoController != null)
|
if (videoController != null)
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Transform.flip(
|
child: VideoPlayer(videoController!),
|
||||||
flipX: widget.mirrorVideo,
|
|
||||||
child: VideoPlayer(videoController!),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Screenshot(
|
Screenshot(
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ 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/contacts.dao.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.dart';
|
import 'package:twonly/src/views/camera/share_image_components/best_friends_selector.dart';
|
||||||
import 'package:twonly/src/views/components/flame.dart';
|
import 'package:twonly/src/views/components/flame.dart';
|
||||||
|
|
@ -21,25 +22,19 @@ import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
||||||
class ShareImageView extends StatefulWidget {
|
class ShareImageView extends StatefulWidget {
|
||||||
const ShareImageView({
|
const ShareImageView({
|
||||||
required this.imageBytesFuture,
|
required this.imageBytesFuture,
|
||||||
required this.isRealTwonly,
|
|
||||||
required this.mirrorVideo,
|
|
||||||
required this.maxShowTime,
|
|
||||||
required this.selectedUserIds,
|
required this.selectedUserIds,
|
||||||
required this.updateStatus,
|
required this.updateStatus,
|
||||||
required this.videoUploadHandler,
|
required this.videoUploadHandler,
|
||||||
required this.mediaUploadId,
|
required this.mediaFileService,
|
||||||
super.key,
|
super.key,
|
||||||
this.enableVideoAudio,
|
this.enableVideoAudio,
|
||||||
});
|
});
|
||||||
final Future<Uint8List?> imageBytesFuture;
|
final Future<Uint8List?> imageBytesFuture;
|
||||||
final bool isRealTwonly;
|
|
||||||
final bool mirrorVideo;
|
|
||||||
final int maxShowTime;
|
|
||||||
final HashSet<int> selectedUserIds;
|
final HashSet<int> selectedUserIds;
|
||||||
final bool? enableVideoAudio;
|
final bool? enableVideoAudio;
|
||||||
final int mediaUploadId;
|
|
||||||
final void Function(int, bool) updateStatus;
|
final void Function(int, bool) updateStatus;
|
||||||
final Future<bool>? videoUploadHandler;
|
final Future<bool>? videoUploadHandler;
|
||||||
|
final MediaFileService mediaFileService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ShareImageView> createState() => _ShareImageView();
|
State<ShareImageView> createState() => _ShareImageView();
|
||||||
|
|
|
||||||
|
|
@ -227,8 +227,7 @@ class ContactsListView extends StatelessWidget {
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.close, color: Colors.red),
|
icon: const Icon(Icons.close, color: Colors.red),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await rejectUser(contact.userId);
|
await rejectAndDeleteContact(contact.userId);
|
||||||
await deleteContact(contact.userId);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/message_old.dart';
|
import 'package:twonly/src/model/json/message_old.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart' as received;
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart'
|
||||||
|
as received;
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/message_old.dart';
|
import 'package:twonly/src/model/json/message_old.dart';
|
||||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@ class _ContactViewState extends State<ContactView> {
|
||||||
);
|
);
|
||||||
if (remove) {
|
if (remove) {
|
||||||
// trigger deletion for the other user...
|
// trigger deletion for the other user...
|
||||||
await rejectUser(contact.userId);
|
await rejectAndDeleteContact(contact.userId);
|
||||||
await deleteContact(contact.userId);
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.popUntil(context, (route) => route.isFirst);
|
Navigator.popUntil(context, (route) => route.isFirst);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart' as send;
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||||
import 'package:twonly/src/services/thumbnail.service.dart';
|
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
||||||
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ import 'package:photo_view/photo_view_gallery.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart' as received;
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart'
|
||||||
import 'package:twonly/src/services/api/media_upload.dart' as send;
|
as received;
|
||||||
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,8 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
await const FlutterSecureStorage()
|
await const FlutterSecureStorage()
|
||||||
.write(key: SecureStorageKeys.userData, value: jsonEncode(userData));
|
.write(key: SecureStorageKeys.userData, value: jsonEncode(userData));
|
||||||
|
|
||||||
|
gUser = userData;
|
||||||
|
|
||||||
await apiService.authenticate();
|
await apiService.authenticate();
|
||||||
widget.callbackOnSuccess();
|
widget.callbackOnSuccess();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart'
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart'
|
||||||
show createDownloadToken, uint8ListToHex;
|
show createDownloadToken, uint8ListToHex;
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:hashlib/random.dart';
|
import 'package:hashlib/random.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/pow.dart';
|
import 'package:twonly/src/utils/pow.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue