mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:48:41 +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/providers/connection.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/views/components/app_outdated.dart';
|
||||
import 'package:twonly/src/views/home.view.dart';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:camera/camera.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';
|
||||
|
||||
late ApiService apiService;
|
||||
|
|
@ -9,6 +10,10 @@ late TwonlyDB twonlyDB;
|
|||
|
||||
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 UI when something changed. The callbacks will be set by
|
||||
// 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/settings.provider.dart';
|
||||
import 'package:twonly/src/services/api.service.dart';
|
||||
import 'package:twonly/src/services/api/media_download.dart';
|
||||
import 'package:twonly/src/services/api/media_upload.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/fcm.service.dart';
|
||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||
|
|
@ -28,6 +28,7 @@ void main() async {
|
|||
|
||||
final user = await getUser();
|
||||
if (user != null) {
|
||||
gUser = user;
|
||||
if (user.isDemoUser) {
|
||||
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/messages.table.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';
|
||||
|
||||
part 'messages.dao.g.dart';
|
||||
|
|
|
|||
|
|
@ -10,10 +10,21 @@ enum MediaType {
|
|||
}
|
||||
|
||||
enum UploadState {
|
||||
pending,
|
||||
readyToUpload,
|
||||
uploadTaskStarted,
|
||||
receiverNotified,
|
||||
// Image/Video was taken. A database entry was created to track it...
|
||||
initialized,
|
||||
// Image was stored but not send
|
||||
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 {
|
||||
|
|
@ -33,7 +44,8 @@ class MediaFiles extends Table {
|
|||
TextColumn get uploadState => textEnum<UploadState>().nullable()();
|
||||
TextColumn get downloadState => textEnum<DownloadState>().nullable()();
|
||||
|
||||
BoolColumn get requiresAuthentication => boolean()();
|
||||
BoolColumn get requiresAuthentication =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get reopenByContact =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class Messages extends Table {
|
|||
TextColumn get mediaId =>
|
||||
text().nullable().references(MediaFiles, #mediaId)();
|
||||
|
||||
BlobColumn get downloadToken => blob().nullable()();
|
||||
|
||||
TextColumn get quotesMessageId =>
|
||||
text().nullable().references(Messages, #messageId)();
|
||||
|
||||
|
|
|
|||
|
|
@ -1460,9 +1460,10 @@ class $MediaFilesTable extends MediaFiles
|
|||
late final GeneratedColumn<bool> requiresAuthentication =
|
||||
GeneratedColumn<bool>('requires_authentication', aliasedName, false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: true,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("requires_authentication" IN (0, 1))'));
|
||||
'CHECK ("requires_authentication" IN (0, 1))'),
|
||||
defaultValue: const Constant(false));
|
||||
static const VerificationMeta _reopenByContactMeta =
|
||||
const VerificationMeta('reopenByContact');
|
||||
@override
|
||||
|
|
@ -1563,8 +1564,6 @@ class $MediaFilesTable extends MediaFiles
|
|||
_requiresAuthenticationMeta,
|
||||
requiresAuthentication.isAcceptableOrUnknown(
|
||||
data['requires_authentication']!, _requiresAuthenticationMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_requiresAuthenticationMeta);
|
||||
}
|
||||
if (data.containsKey('reopen_by_contact')) {
|
||||
context.handle(
|
||||
|
|
@ -2017,7 +2016,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
required MediaType type,
|
||||
this.uploadState = const Value.absent(),
|
||||
this.downloadState = const Value.absent(),
|
||||
required bool requiresAuthentication,
|
||||
this.requiresAuthentication = const Value.absent(),
|
||||
this.reopenByContact = const Value.absent(),
|
||||
this.stored = const Value.absent(),
|
||||
this.reuploadRequestedBy = const Value.absent(),
|
||||
|
|
@ -2028,8 +2027,7 @@ class MediaFilesCompanion extends UpdateCompanion<MediaFile> {
|
|||
this.encryptionNonce = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : type = Value(type),
|
||||
requiresAuthentication = Value(requiresAuthentication);
|
||||
}) : type = Value(type);
|
||||
static Insertable<MediaFile> custom({
|
||||
Expression<String>? mediaId,
|
||||
Expression<String>? type,
|
||||
|
|
@ -2233,6 +2231,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'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 =
|
||||
const VerificationMeta('quotesMessageId');
|
||||
@override
|
||||
|
|
@ -2319,6 +2323,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
senderId,
|
||||
content,
|
||||
mediaId,
|
||||
downloadToken,
|
||||
quotesMessageId,
|
||||
isDeletedFromSender,
|
||||
isEdited,
|
||||
|
|
@ -2361,6 +2366,12 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
context.handle(_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')) {
|
||||
context.handle(
|
||||
_quotesMessageIdMeta,
|
||||
|
|
@ -2428,6 +2439,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
.read(DriftSqlType.string, data['${effectivePrefix}content']),
|
||||
mediaId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}media_id']),
|
||||
downloadToken: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.blob, data['${effectivePrefix}download_token']),
|
||||
quotesMessageId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string, data['${effectivePrefix}quotes_message_id']),
|
||||
isDeletedFromSender: attachedDatabase.typeMapping.read(
|
||||
|
|
@ -2461,6 +2474,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
final int? senderId;
|
||||
final String? content;
|
||||
final String? mediaId;
|
||||
final Uint8List? downloadToken;
|
||||
final String? quotesMessageId;
|
||||
final bool isDeletedFromSender;
|
||||
final bool isEdited;
|
||||
|
|
@ -2476,6 +2490,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
this.senderId,
|
||||
this.content,
|
||||
this.mediaId,
|
||||
this.downloadToken,
|
||||
this.quotesMessageId,
|
||||
required this.isDeletedFromSender,
|
||||
required this.isEdited,
|
||||
|
|
@ -2499,6 +2514,9 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
if (!nullToAbsent || mediaId != null) {
|
||||
map['media_id'] = Variable<String>(mediaId);
|
||||
}
|
||||
if (!nullToAbsent || downloadToken != null) {
|
||||
map['download_token'] = Variable<Uint8List>(downloadToken);
|
||||
}
|
||||
if (!nullToAbsent || quotesMessageId != null) {
|
||||
map['quotes_message_id'] = Variable<String>(quotesMessageId);
|
||||
}
|
||||
|
|
@ -2530,6 +2548,9 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
mediaId: mediaId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(mediaId),
|
||||
downloadToken: downloadToken == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(downloadToken),
|
||||
quotesMessageId: quotesMessageId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(quotesMessageId),
|
||||
|
|
@ -2557,6 +2578,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
senderId: serializer.fromJson<int?>(json['senderId']),
|
||||
content: serializer.fromJson<String?>(json['content']),
|
||||
mediaId: serializer.fromJson<String?>(json['mediaId']),
|
||||
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
||||
quotesMessageId: serializer.fromJson<String?>(json['quotesMessageId']),
|
||||
isDeletedFromSender:
|
||||
serializer.fromJson<bool>(json['isDeletedFromSender']),
|
||||
|
|
@ -2578,6 +2600,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
'senderId': serializer.toJson<int?>(senderId),
|
||||
'content': serializer.toJson<String?>(content),
|
||||
'mediaId': serializer.toJson<String?>(mediaId),
|
||||
'downloadToken': serializer.toJson<Uint8List?>(downloadToken),
|
||||
'quotesMessageId': serializer.toJson<String?>(quotesMessageId),
|
||||
'isDeletedFromSender': serializer.toJson<bool>(isDeletedFromSender),
|
||||
'isEdited': serializer.toJson<bool>(isEdited),
|
||||
|
|
@ -2596,6 +2619,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
Value<int?> senderId = const Value.absent(),
|
||||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<String?> quotesMessageId = const Value.absent(),
|
||||
bool? isDeletedFromSender,
|
||||
bool? isEdited,
|
||||
|
|
@ -2611,6 +2635,8 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
senderId: senderId.present ? senderId.value : this.senderId,
|
||||
content: content.present ? content.value : this.content,
|
||||
mediaId: mediaId.present ? mediaId.value : this.mediaId,
|
||||
downloadToken:
|
||||
downloadToken.present ? downloadToken.value : this.downloadToken,
|
||||
quotesMessageId: quotesMessageId.present
|
||||
? quotesMessageId.value
|
||||
: this.quotesMessageId,
|
||||
|
|
@ -2630,6 +2656,9 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
senderId: data.senderId.present ? data.senderId.value : this.senderId,
|
||||
content: data.content.present ? data.content.value : this.content,
|
||||
mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId,
|
||||
downloadToken: data.downloadToken.present
|
||||
? data.downloadToken.value
|
||||
: this.downloadToken,
|
||||
quotesMessageId: data.quotesMessageId.present
|
||||
? data.quotesMessageId.value
|
||||
: this.quotesMessageId,
|
||||
|
|
@ -2658,6 +2687,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
..write('senderId: $senderId, ')
|
||||
..write('content: $content, ')
|
||||
..write('mediaId: $mediaId, ')
|
||||
..write('downloadToken: $downloadToken, ')
|
||||
..write('quotesMessageId: $quotesMessageId, ')
|
||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||
..write('isEdited: $isEdited, ')
|
||||
|
|
@ -2678,6 +2708,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
senderId,
|
||||
content,
|
||||
mediaId,
|
||||
$driftBlobEquality.hash(downloadToken),
|
||||
quotesMessageId,
|
||||
isDeletedFromSender,
|
||||
isEdited,
|
||||
|
|
@ -2696,6 +2727,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
other.senderId == this.senderId &&
|
||||
other.content == this.content &&
|
||||
other.mediaId == this.mediaId &&
|
||||
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
||||
other.quotesMessageId == this.quotesMessageId &&
|
||||
other.isDeletedFromSender == this.isDeletedFromSender &&
|
||||
other.isEdited == this.isEdited &&
|
||||
|
|
@ -2713,6 +2745,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
final Value<int?> senderId;
|
||||
final Value<String?> content;
|
||||
final Value<String?> mediaId;
|
||||
final Value<Uint8List?> downloadToken;
|
||||
final Value<String?> quotesMessageId;
|
||||
final Value<bool> isDeletedFromSender;
|
||||
final Value<bool> isEdited;
|
||||
|
|
@ -2729,6 +2762,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
this.senderId = const Value.absent(),
|
||||
this.content = const Value.absent(),
|
||||
this.mediaId = const Value.absent(),
|
||||
this.downloadToken = const Value.absent(),
|
||||
this.quotesMessageId = const Value.absent(),
|
||||
this.isDeletedFromSender = const Value.absent(),
|
||||
this.isEdited = const Value.absent(),
|
||||
|
|
@ -2746,6 +2780,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
this.senderId = const Value.absent(),
|
||||
this.content = const Value.absent(),
|
||||
this.mediaId = const Value.absent(),
|
||||
this.downloadToken = const Value.absent(),
|
||||
this.quotesMessageId = const Value.absent(),
|
||||
this.isDeletedFromSender = const Value.absent(),
|
||||
this.isEdited = const Value.absent(),
|
||||
|
|
@ -2763,6 +2798,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
Expression<int>? senderId,
|
||||
Expression<String>? content,
|
||||
Expression<String>? mediaId,
|
||||
Expression<Uint8List>? downloadToken,
|
||||
Expression<String>? quotesMessageId,
|
||||
Expression<bool>? isDeletedFromSender,
|
||||
Expression<bool>? isEdited,
|
||||
|
|
@ -2780,6 +2816,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
if (senderId != null) 'sender_id': senderId,
|
||||
if (content != null) 'content': content,
|
||||
if (mediaId != null) 'media_id': mediaId,
|
||||
if (downloadToken != null) 'download_token': downloadToken,
|
||||
if (quotesMessageId != null) 'quotes_message_id': quotesMessageId,
|
||||
if (isDeletedFromSender != null)
|
||||
'is_deleted_from_sender': isDeletedFromSender,
|
||||
|
|
@ -2800,6 +2837,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
Value<int?>? senderId,
|
||||
Value<String?>? content,
|
||||
Value<String?>? mediaId,
|
||||
Value<Uint8List?>? downloadToken,
|
||||
Value<String?>? quotesMessageId,
|
||||
Value<bool>? isDeletedFromSender,
|
||||
Value<bool>? isEdited,
|
||||
|
|
@ -2816,6 +2854,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
senderId: senderId ?? this.senderId,
|
||||
content: content ?? this.content,
|
||||
mediaId: mediaId ?? this.mediaId,
|
||||
downloadToken: downloadToken ?? this.downloadToken,
|
||||
quotesMessageId: quotesMessageId ?? this.quotesMessageId,
|
||||
isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender,
|
||||
isEdited: isEdited ?? this.isEdited,
|
||||
|
|
@ -2847,6 +2886,9 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
if (mediaId.present) {
|
||||
map['media_id'] = Variable<String>(mediaId.value);
|
||||
}
|
||||
if (downloadToken.present) {
|
||||
map['download_token'] = Variable<Uint8List>(downloadToken.value);
|
||||
}
|
||||
if (quotesMessageId.present) {
|
||||
map['quotes_message_id'] = Variable<String>(quotesMessageId.value);
|
||||
}
|
||||
|
|
@ -2888,6 +2930,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
..write('senderId: $senderId, ')
|
||||
..write('content: $content, ')
|
||||
..write('mediaId: $mediaId, ')
|
||||
..write('downloadToken: $downloadToken, ')
|
||||
..write('quotesMessageId: $quotesMessageId, ')
|
||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||
..write('isEdited: $isEdited, ')
|
||||
|
|
@ -7100,7 +7143,7 @@ typedef $$MediaFilesTableCreateCompanionBuilder = MediaFilesCompanion Function({
|
|||
required MediaType type,
|
||||
Value<UploadState?> uploadState,
|
||||
Value<DownloadState?> downloadState,
|
||||
required bool requiresAuthentication,
|
||||
Value<bool> requiresAuthentication,
|
||||
Value<bool> reopenByContact,
|
||||
Value<bool> stored,
|
||||
Value<List<int>?> reuploadRequestedBy,
|
||||
|
|
@ -7433,7 +7476,7 @@ class $$MediaFilesTableTableManager extends RootTableManager<
|
|||
required MediaType type,
|
||||
Value<UploadState?> uploadState = 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> stored = const Value.absent(),
|
||||
Value<List<int>?> reuploadRequestedBy = const Value.absent(),
|
||||
|
|
@ -7513,6 +7556,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
|
|||
Value<int?> senderId,
|
||||
Value<String?> content,
|
||||
Value<String?> mediaId,
|
||||
Value<Uint8List?> downloadToken,
|
||||
Value<String?> quotesMessageId,
|
||||
Value<bool> isDeletedFromSender,
|
||||
Value<bool> isEdited,
|
||||
|
|
@ -7530,6 +7574,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
|
|||
Value<int?> senderId,
|
||||
Value<String?> content,
|
||||
Value<String?> mediaId,
|
||||
Value<Uint8List?> downloadToken,
|
||||
Value<String?> quotesMessageId,
|
||||
Value<bool> isDeletedFromSender,
|
||||
Value<bool> isEdited,
|
||||
|
|
@ -7671,6 +7716,9 @@ class $$MessagesTableFilterComposer
|
|||
ColumnFilters<String> get content => $composableBuilder(
|
||||
column: $table.content, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<bool> get isDeletedFromSender => $composableBuilder(
|
||||
column: $table.isDeletedFromSender,
|
||||
builder: (column) => ColumnFilters(column));
|
||||
|
|
@ -7856,6 +7904,10 @@ class $$MessagesTableOrderingComposer
|
|||
ColumnOrderings<String> get content => $composableBuilder(
|
||||
column: $table.content, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<bool> get isDeletedFromSender => $composableBuilder(
|
||||
column: $table.isDeletedFromSender,
|
||||
builder: (column) => ColumnOrderings(column));
|
||||
|
|
@ -7978,6 +8030,9 @@ class $$MessagesTableAnnotationComposer
|
|||
GeneratedColumn<String> get content =>
|
||||
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<Uint8List> get downloadToken => $composableBuilder(
|
||||
column: $table.downloadToken, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get isDeletedFromSender => $composableBuilder(
|
||||
column: $table.isDeletedFromSender, builder: (column) => column);
|
||||
|
||||
|
|
@ -8181,6 +8236,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
Value<int?> senderId = const Value.absent(),
|
||||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<String?> quotesMessageId = const Value.absent(),
|
||||
Value<bool> isDeletedFromSender = const Value.absent(),
|
||||
Value<bool> isEdited = const Value.absent(),
|
||||
|
|
@ -8198,6 +8254,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
senderId: senderId,
|
||||
content: content,
|
||||
mediaId: mediaId,
|
||||
downloadToken: downloadToken,
|
||||
quotesMessageId: quotesMessageId,
|
||||
isDeletedFromSender: isDeletedFromSender,
|
||||
isEdited: isEdited,
|
||||
|
|
@ -8215,6 +8272,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
Value<int?> senderId = const Value.absent(),
|
||||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
Value<Uint8List?> downloadToken = const Value.absent(),
|
||||
Value<String?> quotesMessageId = const Value.absent(),
|
||||
Value<bool> isDeletedFromSender = const Value.absent(),
|
||||
Value<bool> isEdited = const Value.absent(),
|
||||
|
|
@ -8232,6 +8290,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
senderId: senderId,
|
||||
content: content,
|
||||
mediaId: mediaId,
|
||||
downloadToken: downloadToken,
|
||||
quotesMessageId: quotesMessageId,
|
||||
isDeletedFromSender: isDeletedFromSender,
|
||||
isEdited: isEdited,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import 'package:drift/drift.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.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/thumbnail.service.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||
|
||||
class 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'
|
||||
as server;
|
||||
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/media_upload.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/api/messages.dart';
|
||||
import 'package:twonly/src/services/api/server_messages.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/twonly.db.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/mediafile.service.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
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:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:cryptography_flutter_plus/cryptography_flutter_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:twonly/globals.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/messages_table.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.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/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.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/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.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: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:
|
||||
/// when user recorded an video
|
||||
/// 1. Compress video
|
||||
|
|
@ -115,43 +43,43 @@ Future<void> initFileDownloader() async {
|
|||
|
||||
/// Create a new entry in the database
|
||||
|
||||
Future<bool> checkForFailedUploads() async {
|
||||
final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload();
|
||||
final mediaUploadIds = <int>[];
|
||||
for (final message in messages) {
|
||||
if (mediaUploadIds.contains(message.mediaUploadId)) {
|
||||
continue;
|
||||
}
|
||||
final affectedRows = await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||
message.mediaUploadId!,
|
||||
const MediaUploadsCompanion(
|
||||
state: Value(UploadState.pending),
|
||||
encryptionData: Value(
|
||||
null, // start from scratch e.q. encrypt the files again if already happen
|
||||
),
|
||||
),
|
||||
);
|
||||
if (affectedRows == 0) {
|
||||
Log.error(
|
||||
'The media from message ${message.messageId} already deleted.',
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
message.messageId,
|
||||
const MessagesCompanion(
|
||||
errorWhileSending: Value(true),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
mediaUploadIds.add(message.mediaUploadId!);
|
||||
}
|
||||
}
|
||||
if (messages.isNotEmpty) {
|
||||
Log.error(
|
||||
'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
|
||||
}
|
||||
// Future<bool> checkForFailedUploads() async {
|
||||
// final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload();
|
||||
// final mediaUploadIds = <int>[];
|
||||
// for (final message in messages) {
|
||||
// if (mediaUploadIds.contains(message.mediaUploadId)) {
|
||||
// continue;
|
||||
// }
|
||||
// final affectedRows = await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||
// message.mediaUploadId!,
|
||||
// const MediaUploadsCompanion(
|
||||
// state: Value(UploadState.pending),
|
||||
// encryptionData: Value(
|
||||
// null, // start from scratch e.q. encrypt the files again if already happen
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// if (affectedRows == 0) {
|
||||
// Log.error(
|
||||
// 'The media from message ${message.messageId} already deleted.',
|
||||
// );
|
||||
// await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
// message.messageId,
|
||||
// const MessagesCompanion(
|
||||
// errorWhileSending: Value(true),
|
||||
// ),
|
||||
// );
|
||||
// } else {
|
||||
// mediaUploadIds.add(message.mediaUploadId!);
|
||||
// }
|
||||
// }
|
||||
// if (messages.isNotEmpty) {
|
||||
// Log.error(
|
||||
// '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
|
||||
// }
|
||||
|
||||
final lockingHandleMediaFile = Mutex();
|
||||
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 {
|
||||
return twonlyDB.mediaUploadsDao
|
||||
.insertMediaUpload(const MediaUploadsCompanion());
|
||||
}
|
||||
|
||||
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,
|
||||
Future<MediaFileService?> initializeMediaUpload(
|
||||
MediaType type,
|
||||
int? displayLimitInMilliseconds,
|
||||
) async {
|
||||
Uint8List imageBytesCompressed;
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final encryptionKey = await (await chacha20.newSecretKey()).extract();
|
||||
final encryptionNonce = chacha20.newNonce();
|
||||
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
Log.info('Raw images size in bytes: ${imageBytes.length}');
|
||||
|
||||
try {
|
||||
imageBytesCompressed = await FlutterImageCompress.compressWithList(
|
||||
format: CompressFormat.webp,
|
||||
// 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),
|
||||
final mediaFile = await twonlyDB.mediaFilesDao.insertMedia(
|
||||
MediaFilesCompanion(
|
||||
uploadState: const Value(UploadState.initialized),
|
||||
displayLimitInMilliseconds: Value(displayLimitInMilliseconds),
|
||||
encryptionKey: Value(Uint8List.fromList(encryptionKey.bytes)),
|
||||
encryptionNonce: Value(Uint8List.fromList(encryptionNonce)),
|
||||
type: Value(type),
|
||||
),
|
||||
);
|
||||
return imageBytesCompressed;
|
||||
if (mediaFile == null) return null;
|
||||
return MediaFileService.fromMedia(mediaFile);
|
||||
}
|
||||
|
||||
Future<void> handlePreProcessingState(MediaUpload media) async {
|
||||
|
|
@ -304,7 +175,6 @@ Future<void> encryptMediaFiles(
|
|||
final state = MediaEncryptionData();
|
||||
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final secretKey = await (await chacha20.newSecretKey()).extract();
|
||||
|
||||
state
|
||||
..encryptionKey = secretKey.bytes
|
||||
|
|
@ -338,70 +208,37 @@ Future<void> encryptMediaFiles(
|
|||
}
|
||||
|
||||
Future<void> finalizeUpload(
|
||||
int mediaUploadId,
|
||||
List<int> contactIds,
|
||||
bool isRealTwonly,
|
||||
bool isVideo,
|
||||
bool mirrorVideo,
|
||||
int maxShowTime,
|
||||
MediaFileService mediaService,
|
||||
List<String> groupIds,
|
||||
) async {
|
||||
final metadata = MediaUploadMetadata()
|
||||
..contactIds = contactIds
|
||||
..isRealTwonly = isRealTwonly
|
||||
..messageSendAt = DateTime.now()
|
||||
..isVideo = isVideo
|
||||
..maxShowTime = maxShowTime
|
||||
..mirrorVideo = mirrorVideo;
|
||||
final messageIds = <Message>[];
|
||||
|
||||
final messageIds = <int>[];
|
||||
|
||||
for (final contactId in contactIds) {
|
||||
final messageId = await twonlyDB.messagesDao.insertMessage(
|
||||
for (final groupId in groupIds) {
|
||||
final message = await twonlyDB.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
contactId: Value(contactId),
|
||||
kind: const Value(MessageKind.media),
|
||||
sendAt: Value(metadata.messageSendAt),
|
||||
downloadState: const Value(DownloadState.pending),
|
||||
mediaUploadId: Value(mediaUploadId),
|
||||
contentJson: Value(
|
||||
jsonEncode(
|
||||
MediaMessageContent(
|
||||
maxShowTime: maxShowTime,
|
||||
isRealTwonly: isRealTwonly,
|
||||
isVideo: isVideo,
|
||||
mirrorVideo: mirrorVideo,
|
||||
).toJson(),
|
||||
),
|
||||
),
|
||||
groupId: Value(groupId),
|
||||
mediaId: Value(mediaService.mediaFile.mediaId),
|
||||
),
|
||||
);
|
||||
if (message != null) {
|
||||
messageIds.add(message);
|
||||
// de-archive contact when sending a new message
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contactId,
|
||||
const ContactsCompanion(
|
||||
await twonlyDB.groupsDao.updateGroup(
|
||||
message.groupId,
|
||||
const GroupsCompanion(
|
||||
archived: Value(false),
|
||||
),
|
||||
);
|
||||
if (messageId != null) {
|
||||
messageIds.add(messageId);
|
||||
} else {
|
||||
Log.error('Error inserting media upload message in database.');
|
||||
}
|
||||
}
|
||||
|
||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
||||
mediaUploadId,
|
||||
MediaUploadsCompanion(
|
||||
messageIds: Value(messageIds),
|
||||
metadata: Value(metadata),
|
||||
),
|
||||
);
|
||||
|
||||
unawaited(handleNextMediaUploadSteps(mediaUploadId));
|
||||
unawaited(handleNextMediaUploadSteps(mediaService.mediaFile.mediaId));
|
||||
}
|
||||
|
||||
final lockingHandleNextMediaUploadStep = Mutex();
|
||||
Future<void> handleNextMediaUploadSteps(int mediaUploadId) async {
|
||||
Future<void> handleNextMediaUploadSteps(String mediaUploadId) async {
|
||||
await lockingHandleNextMediaUploadStep.protect(() async {
|
||||
final mediaUpload = await twonlyDB.mediaUploadsDao
|
||||
.getMediaUploadById(mediaUploadId)
|
||||
|
|
@ -549,7 +386,7 @@ Future<void> handleMediaUpload(MediaUpload media) async {
|
|||
continue;
|
||||
}
|
||||
|
||||
final downloadToken = createDownloadToken();
|
||||
final downloadToken = getRandomUint8List(32);
|
||||
|
||||
final msg = MessageJson(
|
||||
kind: MessageKind.media,
|
||||
|
|
@ -734,159 +571,14 @@ Future<void> uploadFileFast(
|
|||
Log.info('Upload successful!');
|
||||
await handleUploadSuccess(media);
|
||||
return;
|
||||
} else if (response.statusCode == 429) {
|
||||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
media.mediaId,
|
||||
const MediaFilesCompanion(
|
||||
uploadState: Value(UploadState.uploadLimitReached),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
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/twonly.db.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/mediafile.service.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
Future<void> handleMedia(
|
||||
|
|
@ -157,7 +157,7 @@ Future<void> handleMediaUpdate(
|
|||
await twonlyDB.mediaFilesDao.updateMedia(
|
||||
mediaFile.mediaId,
|
||||
MediaFilesCompanion(
|
||||
uploadState: const Value(UploadState.pending),
|
||||
uploadState: const Value(UploadState.uploading),
|
||||
reuploadRequestedBy: Value(reuploadRequestedBy),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -57,13 +57,7 @@ ClientToServer createClientToServerFromApplicationData(
|
|||
return ClientToServer()..v0 = v0;
|
||||
}
|
||||
|
||||
Future<void> deleteContact(int contactId) async {
|
||||
await twonlyDB.signalDao.deleteAllByContactId(contactId);
|
||||
await deleteSessionWithTarget(contactId);
|
||||
await twonlyDB.contactsDao.deleteContactByUserId(contactId);
|
||||
}
|
||||
|
||||
Future<void> rejectUser(int contactId) async {
|
||||
Future<void> rejectAndDeleteContact(int contactId) async {
|
||||
await sendCipherText(
|
||||
contactId,
|
||||
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 {
|
||||
|
|
|
|||
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 'package:drift/drift.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/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';
|
||||
|
||||
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 {
|
||||
if (!storedPath.existsSync()) {
|
||||
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() {
|
||||
if (tempPath.existsSync()) {
|
||||
tempPath.deleteSync();
|
||||
final pathsToRemove = [
|
||||
tempPath,
|
||||
encryptedPath,
|
||||
originalPath,
|
||||
storedPath,
|
||||
thumbnailPath
|
||||
];
|
||||
|
||||
for (final path in pathsToRemove) {
|
||||
if (path.existsSync()) {
|
||||
path.deleteSync();
|
||||
}
|
||||
if (encryptedPath.existsSync()) {
|
||||
encryptedPath.deleteSync();
|
||||
}
|
||||
if (storedPath.existsSync()) {
|
||||
storedPath.deleteSync();
|
||||
}
|
||||
if (thumbnailPath.existsSync()) {
|
||||
thumbnailPath.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,4 +160,8 @@ class MediaFileService {
|
|||
'tmp',
|
||||
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:http/http.dart' as http;
|
||||
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/utils/log.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/model/json/userdata.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/utils/log.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -11,14 +9,13 @@ import 'package:local_auth/local_auth.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/database/twonly.db.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/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
extension ShortCutsExtension on BuildContext {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -245,31 +242,19 @@ String formatBytes(int bytes, {int decimalPlaces = 2}) {
|
|||
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) {
|
||||
final timestamp1 = int.parse(uuid1.substring(0, 8), radix: 16);
|
||||
final timestamp2 = int.parse(uuid2.substring(0, 8), radix: 16);
|
||||
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:path_provider/path_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/model/json/userdata.dart';
|
||||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
|
|
@ -14,6 +15,7 @@ Future<bool> isUserCreated() async {
|
|||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
gUser = user;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +58,8 @@ Future<UserData?> updateUserdata(
|
|||
final updated = updateUser(user);
|
||||
await const FlutterSecureStorage()
|
||||
.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:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:twonly/src/services/api/media_upload.dart';
|
||||
import 'package:twonly/src/services/thumbnail.service.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||
import 'package:twonly/src/utils/misc.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:twonly/globals.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/services/api/mediafiles/upload.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
|
@ -299,17 +301,35 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
File? videoFilePath, {
|
||||
bool sharedFromGallery = false,
|
||||
}) 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(
|
||||
context,
|
||||
PageRouteBuilder(
|
||||
opaque: false,
|
||||
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
||||
videoFilePath: videoFilePath,
|
||||
imageBytes: imageBytes,
|
||||
imageBytesFuture: imageBytes,
|
||||
sharedFromGallery: sharedFromGallery,
|
||||
sendTo: widget.sendTo,
|
||||
mirrorVideo: isFront && Platform.isAndroid && false,
|
||||
useHighQuality: true,
|
||||
mediaFileService: mediaFileService,
|
||||
),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return child;
|
||||
|
|
|
|||
|
|
@ -3,14 +3,19 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:hashlib/random.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/tables/mediafiles.table.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/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
|
@ -34,32 +39,26 @@ const gMediaShowInfinite = 999999;
|
|||
|
||||
class ShareImageEditorView extends StatefulWidget {
|
||||
const ShareImageEditorView({
|
||||
required this.mirrorVideo,
|
||||
required this.useHighQuality,
|
||||
required this.sharedFromGallery,
|
||||
required this.mediaFileService,
|
||||
super.key,
|
||||
this.imageBytes,
|
||||
this.imageBytesFuture,
|
||||
this.sendTo,
|
||||
this.videoFilePath,
|
||||
});
|
||||
final Future<Uint8List?>? imageBytes;
|
||||
final File? videoFilePath;
|
||||
final Contact? sendTo;
|
||||
final bool mirrorVideo;
|
||||
final bool useHighQuality;
|
||||
final Future<Uint8List?>? imageBytesFuture;
|
||||
final Group? sendTo;
|
||||
final bool sharedFromGallery;
|
||||
final MediaFileService mediaFileService;
|
||||
@override
|
||||
State<ShareImageEditorView> createState() => _ShareImageEditorView();
|
||||
}
|
||||
|
||||
class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||
bool _isRealTwonly = false;
|
||||
int maxShowTime = gMediaShowInfinite;
|
||||
double tabDownPosition = 0;
|
||||
bool sendingOrLoadingImage = true;
|
||||
bool loadingImage = true;
|
||||
bool isDisposed = false;
|
||||
HashSet<int> selectedUserIds = HashSet();
|
||||
HashSet<String> selectedGroupIds = HashSet();
|
||||
double widthRatio = 1;
|
||||
double heightRatio = 1;
|
||||
double pixelRatio = 1;
|
||||
|
|
@ -68,26 +67,31 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
ScreenshotController screenshotController = ScreenshotController();
|
||||
|
||||
/// Media upload variables
|
||||
int? mediaUploadId;
|
||||
Future<bool>? videoUploadHandler;
|
||||
|
||||
MediaFileService get mediaService => widget.mediaFileService;
|
||||
MediaFile get media => widget.mediaFileService.mediaFile;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
unawaited(initAsync());
|
||||
unawaited(initMediaFileUpload());
|
||||
|
||||
layers.add(FilterLayerData());
|
||||
|
||||
if (widget.sendTo != null) {
|
||||
selectedUserIds.add(widget.sendTo!.userId);
|
||||
selectedGroupIds.add(widget.sendTo!.groupId);
|
||||
}
|
||||
if (widget.imageBytes != null) {
|
||||
unawaited(loadImage(widget.imageBytes!));
|
||||
} else if (widget.videoFilePath != null) {
|
||||
|
||||
if (widget.imageBytesFuture != null) {
|
||||
unawaited(loadImage(widget.imageBytesFuture!));
|
||||
}
|
||||
|
||||
if (media.type == MediaType.video) {
|
||||
setState(() {
|
||||
sendingOrLoadingImage = false;
|
||||
loadingImage = false;
|
||||
});
|
||||
videoController = VideoPlayerController.file(widget.videoFilePath!);
|
||||
videoController = VideoPlayerController.file(mediaService.originalPath);
|
||||
videoController?.setLooping(true);
|
||||
videoController?.initialize().then((_) async {
|
||||
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
|
||||
void dispose() {
|
||||
isDisposed = true;
|
||||
|
|
@ -128,14 +109,14 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
void updateStatus(int userId, bool checked) {
|
||||
void updateSelectedGroupIds(String groupId, bool checked) {
|
||||
if (checked) {
|
||||
if (_isRealTwonly) {
|
||||
selectedUserIds.clear();
|
||||
if (media.requiresAuthentication) {
|
||||
selectedGroupIds.clear();
|
||||
}
|
||||
selectedUserIds.add(userId);
|
||||
selectedGroupIds.add(groupId);
|
||||
} else {
|
||||
selectedUserIds.remove(userId);
|
||||
selectedGroupIds.remove(groupId);
|
||||
}
|
||||
setState(() {});
|
||||
}
|
||||
|
|
@ -195,38 +176,36 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
NotificationBadge(
|
||||
count: (widget.videoFilePath != null)
|
||||
count: (media.type != MediaType.video)
|
||||
? '0'
|
||||
: maxShowTime == gMediaShowInfinite
|
||||
: media.displayLimitInMilliseconds == null
|
||||
? '∞'
|
||||
: maxShowTime.toString(),
|
||||
: media.displayLimitInMilliseconds.toString(),
|
||||
child: ActionButton(
|
||||
(widget.videoFilePath != null)
|
||||
? maxShowTime == gMediaShowInfinite
|
||||
(media.type != MediaType.video)
|
||||
? media.displayLimitInMilliseconds == null
|
||||
? Icons.repeat_rounded
|
||||
: Icons.repeat_one_rounded
|
||||
: Icons.timer_outlined,
|
||||
tooltipText: context.lang.protectAsARealTwonly,
|
||||
onPressed: () async {
|
||||
if (widget.videoFilePath != null) {
|
||||
setState(() {
|
||||
if (maxShowTime == gMediaShowInfinite) {
|
||||
maxShowTime = 0;
|
||||
} else {
|
||||
maxShowTime = gMediaShowInfinite;
|
||||
}
|
||||
});
|
||||
if (media.type != MediaType.video) {
|
||||
await mediaService.setDisplayLimit(
|
||||
(media.displayLimitInMilliseconds == null) ? 0 : null);
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
return;
|
||||
}
|
||||
if (maxShowTime == gMediaShowInfinite) {
|
||||
int? maxShowTime;
|
||||
if (media.displayLimitInMilliseconds == null) {
|
||||
maxShowTime = 1;
|
||||
} else if (maxShowTime == 1) {
|
||||
} else if (media.displayLimitInMilliseconds == 1) {
|
||||
maxShowTime = 5;
|
||||
} else if (maxShowTime == 5) {
|
||||
} else if (media.displayLimitInMilliseconds == 5) {
|
||||
maxShowTime = 20;
|
||||
} else {
|
||||
maxShowTime = gMediaShowInfinite;
|
||||
}
|
||||
await mediaService.setDisplayLimit(maxShowTime);
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
await updateUserdata((user) {
|
||||
user.defaultShowTime = maxShowTime;
|
||||
|
|
@ -239,15 +218,12 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
ActionButton(
|
||||
FontAwesomeIcons.shieldHeart,
|
||||
tooltipText: context.lang.protectAsARealTwonly,
|
||||
color: _isRealTwonly
|
||||
color: media.requiresAuthentication
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Colors.white,
|
||||
onPressed: () async {
|
||||
_isRealTwonly = !_isRealTwonly;
|
||||
if (_isRealTwonly) {
|
||||
maxShowTime = 12;
|
||||
}
|
||||
selectedUserIds = HashSet();
|
||||
await mediaService.setRequiresAuth(!media.requiresAuthentication);
|
||||
selectedGroupIds = HashSet();
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
|
|
@ -308,11 +284,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
}
|
||||
|
||||
Future<void> pushShareImageView() async {
|
||||
if (mediaUploadId == null) {
|
||||
await initMediaFileUpload();
|
||||
if (mediaUploadId == null) return;
|
||||
}
|
||||
final imageBytes = getMergedImage();
|
||||
final imageBytes = storeImageAsOriginal();
|
||||
await videoController?.pause();
|
||||
if (isDisposed || !mounted) return;
|
||||
final wasSend = await Navigator.push(
|
||||
|
|
@ -320,13 +292,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
MaterialPageRoute(
|
||||
builder: (context) => ShareImageView(
|
||||
imageBytesFuture: imageBytes,
|
||||
isRealTwonly: _isRealTwonly,
|
||||
maxShowTime: maxShowTime,
|
||||
selectedUserIds: selectedUserIds,
|
||||
updateStatus: updateStatus,
|
||||
selectedUserIds: selectedGroupIds,
|
||||
updateStatus: updateSelectedGroupIds,
|
||||
videoUploadHandler: videoUploadHandler,
|
||||
mediaUploadId: mediaUploadId!,
|
||||
mirrorVideo: widget.mirrorVideo,
|
||||
mediaFileService: mediaService,
|
||||
),
|
||||
),
|
||||
) as bool?;
|
||||
|
|
@ -337,36 +306,46 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Uint8List?> getMergedImage() async {
|
||||
Uint8List? image;
|
||||
Future<void> storeImageAsOriginal() async {
|
||||
if (layers.length == 1) {
|
||||
if (layers.first is BackgroundLayerData) {
|
||||
final image = (layers.first as BackgroundLayerData).image.bytes;
|
||||
mediaService.originalPath.writeAsBytesSync(image);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TODO: When changed then create a new mediaID!!!!!!
|
||||
As storedMediaId would overwrite it....
|
||||
|
||||
if (layers.length > 1 || widget.videoFilePath != null) {
|
||||
if (layers.length > 1 || media.type != MediaType.video) {
|
||||
for (final x in layers) {
|
||||
x.showCustomButtons = false;
|
||||
}
|
||||
setState(() {});
|
||||
image = await screenshotController.capture(
|
||||
pixelRatio: (widget.useHighQuality) ? pixelRatio : 1,
|
||||
final image = await screenshotController.capture(
|
||||
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) {
|
||||
x.showCustomButtons = true;
|
||||
}
|
||||
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 {
|
||||
final imageBytes = await imageFile;
|
||||
await currentImage.load(imageBytes);
|
||||
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
|
||||
await currentImage.load(await imageBytesFuture);
|
||||
if (isDisposed) return;
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
|
@ -388,44 +367,18 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
setState(() {
|
||||
sendingOrLoadingImage = true;
|
||||
});
|
||||
final imageBytes = await getMergedImage();
|
||||
if (!context.mounted) return;
|
||||
if (imageBytes == null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
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,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
if (media.type == MediaType.image) {
|
||||
await storeImageAsOriginal();
|
||||
}
|
||||
} else {
|
||||
final imageHandler = addOrModifyImageToUpload(mediaUploadId!, imageBytes);
|
||||
if (media.type == MediaType.video) {
|
||||
Log.error('TODO: COMBINE VIDEO AND IMAGE!!!');
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
// first finalize the upload
|
||||
await finalizeUpload(
|
||||
mediaUploadId!,
|
||||
[widget.sendTo!.userId],
|
||||
_isRealTwonly,
|
||||
widget.videoFilePath != null,
|
||||
widget.mirrorVideo,
|
||||
maxShowTime,
|
||||
);
|
||||
await finalizeUpload(mediaService, [widget.sendTo!.groupId]);
|
||||
|
||||
/// then call the upload process in the background
|
||||
await encryptMediaFiles(
|
||||
|
|
@ -439,7 +392,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
Navigator.pop(context, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -543,11 +495,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
children: [
|
||||
if (videoController != null)
|
||||
Positioned.fill(
|
||||
child: Transform.flip(
|
||||
flipX: widget.mirrorVideo,
|
||||
child: VideoPlayer(videoController!),
|
||||
),
|
||||
),
|
||||
Screenshot(
|
||||
controller: screenshotController,
|
||||
child: LayersViewer(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/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/views/camera/share_image_components/best_friends_selector.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 {
|
||||
const ShareImageView({
|
||||
required this.imageBytesFuture,
|
||||
required this.isRealTwonly,
|
||||
required this.mirrorVideo,
|
||||
required this.maxShowTime,
|
||||
required this.selectedUserIds,
|
||||
required this.updateStatus,
|
||||
required this.videoUploadHandler,
|
||||
required this.mediaUploadId,
|
||||
required this.mediaFileService,
|
||||
super.key,
|
||||
this.enableVideoAudio,
|
||||
});
|
||||
final Future<Uint8List?> imageBytesFuture;
|
||||
final bool isRealTwonly;
|
||||
final bool mirrorVideo;
|
||||
final int maxShowTime;
|
||||
final HashSet<int> selectedUserIds;
|
||||
final bool? enableVideoAudio;
|
||||
final int mediaUploadId;
|
||||
final void Function(int, bool) updateStatus;
|
||||
final Future<bool>? videoUploadHandler;
|
||||
final MediaFileService mediaFileService;
|
||||
|
||||
@override
|
||||
State<ShareImageView> createState() => _ShareImageView();
|
||||
|
|
|
|||
|
|
@ -227,8 +227,7 @@ class ContactsListView extends StatelessWidget {
|
|||
child: IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.red),
|
||||
onPressed: () async {
|
||||
await rejectUser(contact.userId);
|
||||
await deleteContact(contact.userId);
|
||||
await rejectAndDeleteContact(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/model/json/userdata.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/storage.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/memory_item.model.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/utils/misc.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/model/json/message_old.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/utils.dart';
|
||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ class _ContactViewState extends State<ContactView> {
|
|||
);
|
||||
if (remove) {
|
||||
// trigger deletion for the other user...
|
||||
await rejectUser(contact.userId);
|
||||
await deleteContact(contact.userId);
|
||||
await rejectAndDeleteContact(contact.userId);
|
||||
if (mounted) {
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import 'package:intl/intl.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.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/thumbnail.service.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.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_photo_slider.view.dart';
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ import 'package:photo_view/photo_view_gallery.dart';
|
|||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.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/media_upload.dart' as send;
|
||||
import 'package:twonly/src/services/api/mediafiles/download.service.dart'
|
||||
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/views/camera/share_image_editor_view.dart';
|
||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ class _RegisterViewState extends State<RegisterView> {
|
|||
await const FlutterSecureStorage()
|
||||
.write(key: SecureStorageKeys.userData, value: jsonEncode(userData));
|
||||
|
||||
gUser = userData;
|
||||
|
||||
await apiService.authenticate();
|
||||
widget.callbackOnSuccess();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:connectivity_plus/connectivity_plus.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/storage.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import 'package:package_info_plus/package_info_plus.dart';
|
|||
import 'package:twonly/globals.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/services/api/media_upload.dart'
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart'
|
||||
show createDownloadToken, uint8ListToHex;
|
||||
import 'package:twonly/src/utils/log.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: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/pow.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
|
|
|
|||
Loading…
Reference in a new issue