mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
media upload
This commit is contained in:
parent
b2dc384465
commit
645dfe16da
20 changed files with 456 additions and 647 deletions
|
|
@ -10,11 +10,9 @@ 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/mediafiles/download.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/media_background.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/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/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,9 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
await (update(groups)..where((c) => c.groupId.equals(groupId)))
|
await (update(groups)..where((c) => c.groupId.equals(groupId)))
|
||||||
.write(updates);
|
.write(updates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<GroupMember>> getGroupMembers(String groupId) async {
|
||||||
|
return (select(groupMembers)..where((t) => t.groupId.equals(groupId)))
|
||||||
|
.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,14 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
.write(updatedValues);
|
.write(updatedValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateMessagesByMediaId(
|
||||||
|
String mediaId,
|
||||||
|
MessagesCompanion updatedValues,
|
||||||
|
) {
|
||||||
|
return (update(messages)..where((c) => c.mediaId.equals(mediaId)))
|
||||||
|
.write(updatedValues);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Message?> insertMessage(MessagesCompanion message) async {
|
Future<Message?> insertMessage(MessagesCompanion message) async {
|
||||||
try {
|
try {
|
||||||
final rowId = await into(messages).insert(message);
|
final rowId = await into(messages).insert(message);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ class Groups extends Table {
|
||||||
BoolColumn get pinned => boolean().withDefault(const Constant(false))();
|
BoolColumn get pinned => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get archived => boolean().withDefault(const Constant(false))();
|
BoolColumn get archived => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
|
TextColumn get groupName => text()();
|
||||||
|
|
||||||
DateTimeColumn get lastMessageExchange =>
|
DateTimeColumn get lastMessageExchange =>
|
||||||
dateTime().withDefault(currentDateAndTime)();
|
dateTime().withDefault(currentDateAndTime)();
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ enum UploadState {
|
||||||
// Image was stored but not send
|
// Image was stored but not send
|
||||||
storedOnly,
|
storedOnly,
|
||||||
// At this point the user is finished with editing, and the media file can be uploaded
|
// At this point the user is finished with editing, and the media file can be uploaded
|
||||||
compressing,
|
preprocessing,
|
||||||
encrypting,
|
|
||||||
uploading,
|
uploading,
|
||||||
backgroundUploadTaskStarted,
|
backgroundUploadTaskStarted,
|
||||||
uploaded,
|
uploaded,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ class Messages extends Table {
|
||||||
TextColumn get mediaId =>
|
TextColumn get mediaId =>
|
||||||
text().nullable().references(MediaFiles, #mediaId)();
|
text().nullable().references(MediaFiles, #mediaId)();
|
||||||
|
|
||||||
|
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
BlobColumn get downloadToken => blob().nullable()();
|
BlobColumn get downloadToken => blob().nullable()();
|
||||||
|
|
||||||
TextColumn get quotesMessageId =>
|
TextColumn get quotesMessageId =>
|
||||||
|
|
|
||||||
|
|
@ -1061,6 +1061,12 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
||||||
defaultConstraints:
|
defaultConstraints:
|
||||||
GeneratedColumn.constraintIsAlways('CHECK ("archived" IN (0, 1))'),
|
GeneratedColumn.constraintIsAlways('CHECK ("archived" IN (0, 1))'),
|
||||||
defaultValue: const Constant(false));
|
defaultValue: const Constant(false));
|
||||||
|
static const VerificationMeta _groupNameMeta =
|
||||||
|
const VerificationMeta('groupName');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<String> groupName = GeneratedColumn<String>(
|
||||||
|
'group_name', aliasedName, false,
|
||||||
|
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||||
static const VerificationMeta _lastMessageExchangeMeta =
|
static const VerificationMeta _lastMessageExchangeMeta =
|
||||||
const VerificationMeta('lastMessageExchange');
|
const VerificationMeta('lastMessageExchange');
|
||||||
@override
|
@override
|
||||||
|
|
@ -1084,6 +1090,7 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
||||||
isGroupOfTwo,
|
isGroupOfTwo,
|
||||||
pinned,
|
pinned,
|
||||||
archived,
|
archived,
|
||||||
|
groupName,
|
||||||
lastMessageExchange,
|
lastMessageExchange,
|
||||||
createdAt
|
createdAt
|
||||||
];
|
];
|
||||||
|
|
@ -1125,6 +1132,12 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
||||||
context.handle(_archivedMeta,
|
context.handle(_archivedMeta,
|
||||||
archived.isAcceptableOrUnknown(data['archived']!, _archivedMeta));
|
archived.isAcceptableOrUnknown(data['archived']!, _archivedMeta));
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('group_name')) {
|
||||||
|
context.handle(_groupNameMeta,
|
||||||
|
groupName.isAcceptableOrUnknown(data['group_name']!, _groupNameMeta));
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_groupNameMeta);
|
||||||
|
}
|
||||||
if (data.containsKey('last_message_exchange')) {
|
if (data.containsKey('last_message_exchange')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_lastMessageExchangeMeta,
|
_lastMessageExchangeMeta,
|
||||||
|
|
@ -1154,6 +1167,8 @@ class $GroupsTable extends Groups with TableInfo<$GroupsTable, Group> {
|
||||||
.read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!,
|
.read(DriftSqlType.bool, data['${effectivePrefix}pinned'])!,
|
||||||
archived: attachedDatabase.typeMapping
|
archived: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.bool, data['${effectivePrefix}archived'])!,
|
.read(DriftSqlType.bool, data['${effectivePrefix}archived'])!,
|
||||||
|
groupName: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.string, data['${effectivePrefix}group_name'])!,
|
||||||
lastMessageExchange: attachedDatabase.typeMapping.read(
|
lastMessageExchange: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.dateTime,
|
DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}last_message_exchange'])!,
|
data['${effectivePrefix}last_message_exchange'])!,
|
||||||
|
|
@ -1174,6 +1189,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
final bool isGroupOfTwo;
|
final bool isGroupOfTwo;
|
||||||
final bool pinned;
|
final bool pinned;
|
||||||
final bool archived;
|
final bool archived;
|
||||||
|
final String groupName;
|
||||||
final DateTime lastMessageExchange;
|
final DateTime lastMessageExchange;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
const Group(
|
const Group(
|
||||||
|
|
@ -1182,6 +1198,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
required this.isGroupOfTwo,
|
required this.isGroupOfTwo,
|
||||||
required this.pinned,
|
required this.pinned,
|
||||||
required this.archived,
|
required this.archived,
|
||||||
|
required this.groupName,
|
||||||
required this.lastMessageExchange,
|
required this.lastMessageExchange,
|
||||||
required this.createdAt});
|
required this.createdAt});
|
||||||
@override
|
@override
|
||||||
|
|
@ -1192,6 +1209,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
map['is_group_of_two'] = Variable<bool>(isGroupOfTwo);
|
map['is_group_of_two'] = Variable<bool>(isGroupOfTwo);
|
||||||
map['pinned'] = Variable<bool>(pinned);
|
map['pinned'] = Variable<bool>(pinned);
|
||||||
map['archived'] = Variable<bool>(archived);
|
map['archived'] = Variable<bool>(archived);
|
||||||
|
map['group_name'] = Variable<String>(groupName);
|
||||||
map['last_message_exchange'] = Variable<DateTime>(lastMessageExchange);
|
map['last_message_exchange'] = Variable<DateTime>(lastMessageExchange);
|
||||||
map['created_at'] = Variable<DateTime>(createdAt);
|
map['created_at'] = Variable<DateTime>(createdAt);
|
||||||
return map;
|
return map;
|
||||||
|
|
@ -1204,6 +1222,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
isGroupOfTwo: Value(isGroupOfTwo),
|
isGroupOfTwo: Value(isGroupOfTwo),
|
||||||
pinned: Value(pinned),
|
pinned: Value(pinned),
|
||||||
archived: Value(archived),
|
archived: Value(archived),
|
||||||
|
groupName: Value(groupName),
|
||||||
lastMessageExchange: Value(lastMessageExchange),
|
lastMessageExchange: Value(lastMessageExchange),
|
||||||
createdAt: Value(createdAt),
|
createdAt: Value(createdAt),
|
||||||
);
|
);
|
||||||
|
|
@ -1218,6 +1237,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
isGroupOfTwo: serializer.fromJson<bool>(json['isGroupOfTwo']),
|
isGroupOfTwo: serializer.fromJson<bool>(json['isGroupOfTwo']),
|
||||||
pinned: serializer.fromJson<bool>(json['pinned']),
|
pinned: serializer.fromJson<bool>(json['pinned']),
|
||||||
archived: serializer.fromJson<bool>(json['archived']),
|
archived: serializer.fromJson<bool>(json['archived']),
|
||||||
|
groupName: serializer.fromJson<String>(json['groupName']),
|
||||||
lastMessageExchange:
|
lastMessageExchange:
|
||||||
serializer.fromJson<DateTime>(json['lastMessageExchange']),
|
serializer.fromJson<DateTime>(json['lastMessageExchange']),
|
||||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||||
|
|
@ -1232,6 +1252,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
'isGroupOfTwo': serializer.toJson<bool>(isGroupOfTwo),
|
'isGroupOfTwo': serializer.toJson<bool>(isGroupOfTwo),
|
||||||
'pinned': serializer.toJson<bool>(pinned),
|
'pinned': serializer.toJson<bool>(pinned),
|
||||||
'archived': serializer.toJson<bool>(archived),
|
'archived': serializer.toJson<bool>(archived),
|
||||||
|
'groupName': serializer.toJson<String>(groupName),
|
||||||
'lastMessageExchange': serializer.toJson<DateTime>(lastMessageExchange),
|
'lastMessageExchange': serializer.toJson<DateTime>(lastMessageExchange),
|
||||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||||
};
|
};
|
||||||
|
|
@ -1243,6 +1264,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
bool? isGroupOfTwo,
|
bool? isGroupOfTwo,
|
||||||
bool? pinned,
|
bool? pinned,
|
||||||
bool? archived,
|
bool? archived,
|
||||||
|
String? groupName,
|
||||||
DateTime? lastMessageExchange,
|
DateTime? lastMessageExchange,
|
||||||
DateTime? createdAt}) =>
|
DateTime? createdAt}) =>
|
||||||
Group(
|
Group(
|
||||||
|
|
@ -1251,6 +1273,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
isGroupOfTwo: isGroupOfTwo ?? this.isGroupOfTwo,
|
isGroupOfTwo: isGroupOfTwo ?? this.isGroupOfTwo,
|
||||||
pinned: pinned ?? this.pinned,
|
pinned: pinned ?? this.pinned,
|
||||||
archived: archived ?? this.archived,
|
archived: archived ?? this.archived,
|
||||||
|
groupName: groupName ?? this.groupName,
|
||||||
lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange,
|
lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
);
|
);
|
||||||
|
|
@ -1265,6 +1288,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
: this.isGroupOfTwo,
|
: this.isGroupOfTwo,
|
||||||
pinned: data.pinned.present ? data.pinned.value : this.pinned,
|
pinned: data.pinned.present ? data.pinned.value : this.pinned,
|
||||||
archived: data.archived.present ? data.archived.value : this.archived,
|
archived: data.archived.present ? data.archived.value : this.archived,
|
||||||
|
groupName: data.groupName.present ? data.groupName.value : this.groupName,
|
||||||
lastMessageExchange: data.lastMessageExchange.present
|
lastMessageExchange: data.lastMessageExchange.present
|
||||||
? data.lastMessageExchange.value
|
? data.lastMessageExchange.value
|
||||||
: this.lastMessageExchange,
|
: this.lastMessageExchange,
|
||||||
|
|
@ -1280,6 +1304,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
..write('isGroupOfTwo: $isGroupOfTwo, ')
|
..write('isGroupOfTwo: $isGroupOfTwo, ')
|
||||||
..write('pinned: $pinned, ')
|
..write('pinned: $pinned, ')
|
||||||
..write('archived: $archived, ')
|
..write('archived: $archived, ')
|
||||||
|
..write('groupName: $groupName, ')
|
||||||
..write('lastMessageExchange: $lastMessageExchange, ')
|
..write('lastMessageExchange: $lastMessageExchange, ')
|
||||||
..write('createdAt: $createdAt')
|
..write('createdAt: $createdAt')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
|
|
@ -1288,7 +1313,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(groupId, isGroupAdmin, isGroupOfTwo, pinned,
|
int get hashCode => Object.hash(groupId, isGroupAdmin, isGroupOfTwo, pinned,
|
||||||
archived, lastMessageExchange, createdAt);
|
archived, groupName, lastMessageExchange, createdAt);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
|
|
@ -1298,6 +1323,7 @@ class Group extends DataClass implements Insertable<Group> {
|
||||||
other.isGroupOfTwo == this.isGroupOfTwo &&
|
other.isGroupOfTwo == this.isGroupOfTwo &&
|
||||||
other.pinned == this.pinned &&
|
other.pinned == this.pinned &&
|
||||||
other.archived == this.archived &&
|
other.archived == this.archived &&
|
||||||
|
other.groupName == this.groupName &&
|
||||||
other.lastMessageExchange == this.lastMessageExchange &&
|
other.lastMessageExchange == this.lastMessageExchange &&
|
||||||
other.createdAt == this.createdAt);
|
other.createdAt == this.createdAt);
|
||||||
}
|
}
|
||||||
|
|
@ -1308,6 +1334,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
final Value<bool> isGroupOfTwo;
|
final Value<bool> isGroupOfTwo;
|
||||||
final Value<bool> pinned;
|
final Value<bool> pinned;
|
||||||
final Value<bool> archived;
|
final Value<bool> archived;
|
||||||
|
final Value<String> groupName;
|
||||||
final Value<DateTime> lastMessageExchange;
|
final Value<DateTime> lastMessageExchange;
|
||||||
final Value<DateTime> createdAt;
|
final Value<DateTime> createdAt;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
|
|
@ -1317,6 +1344,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
this.isGroupOfTwo = const Value.absent(),
|
this.isGroupOfTwo = const Value.absent(),
|
||||||
this.pinned = const Value.absent(),
|
this.pinned = const Value.absent(),
|
||||||
this.archived = const Value.absent(),
|
this.archived = const Value.absent(),
|
||||||
|
this.groupName = const Value.absent(),
|
||||||
this.lastMessageExchange = const Value.absent(),
|
this.lastMessageExchange = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
|
|
@ -1327,17 +1355,20 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
required bool isGroupOfTwo,
|
required bool isGroupOfTwo,
|
||||||
this.pinned = const Value.absent(),
|
this.pinned = const Value.absent(),
|
||||||
this.archived = const Value.absent(),
|
this.archived = const Value.absent(),
|
||||||
|
required String groupName,
|
||||||
this.lastMessageExchange = const Value.absent(),
|
this.lastMessageExchange = const Value.absent(),
|
||||||
this.createdAt = const Value.absent(),
|
this.createdAt = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : isGroupAdmin = Value(isGroupAdmin),
|
}) : isGroupAdmin = Value(isGroupAdmin),
|
||||||
isGroupOfTwo = Value(isGroupOfTwo);
|
isGroupOfTwo = Value(isGroupOfTwo),
|
||||||
|
groupName = Value(groupName);
|
||||||
static Insertable<Group> custom({
|
static Insertable<Group> custom({
|
||||||
Expression<String>? groupId,
|
Expression<String>? groupId,
|
||||||
Expression<bool>? isGroupAdmin,
|
Expression<bool>? isGroupAdmin,
|
||||||
Expression<bool>? isGroupOfTwo,
|
Expression<bool>? isGroupOfTwo,
|
||||||
Expression<bool>? pinned,
|
Expression<bool>? pinned,
|
||||||
Expression<bool>? archived,
|
Expression<bool>? archived,
|
||||||
|
Expression<String>? groupName,
|
||||||
Expression<DateTime>? lastMessageExchange,
|
Expression<DateTime>? lastMessageExchange,
|
||||||
Expression<DateTime>? createdAt,
|
Expression<DateTime>? createdAt,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
|
|
@ -1348,6 +1379,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
if (isGroupOfTwo != null) 'is_group_of_two': isGroupOfTwo,
|
if (isGroupOfTwo != null) 'is_group_of_two': isGroupOfTwo,
|
||||||
if (pinned != null) 'pinned': pinned,
|
if (pinned != null) 'pinned': pinned,
|
||||||
if (archived != null) 'archived': archived,
|
if (archived != null) 'archived': archived,
|
||||||
|
if (groupName != null) 'group_name': groupName,
|
||||||
if (lastMessageExchange != null)
|
if (lastMessageExchange != null)
|
||||||
'last_message_exchange': lastMessageExchange,
|
'last_message_exchange': lastMessageExchange,
|
||||||
if (createdAt != null) 'created_at': createdAt,
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
|
@ -1361,6 +1393,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
Value<bool>? isGroupOfTwo,
|
Value<bool>? isGroupOfTwo,
|
||||||
Value<bool>? pinned,
|
Value<bool>? pinned,
|
||||||
Value<bool>? archived,
|
Value<bool>? archived,
|
||||||
|
Value<String>? groupName,
|
||||||
Value<DateTime>? lastMessageExchange,
|
Value<DateTime>? lastMessageExchange,
|
||||||
Value<DateTime>? createdAt,
|
Value<DateTime>? createdAt,
|
||||||
Value<int>? rowid}) {
|
Value<int>? rowid}) {
|
||||||
|
|
@ -1370,6 +1403,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
isGroupOfTwo: isGroupOfTwo ?? this.isGroupOfTwo,
|
isGroupOfTwo: isGroupOfTwo ?? this.isGroupOfTwo,
|
||||||
pinned: pinned ?? this.pinned,
|
pinned: pinned ?? this.pinned,
|
||||||
archived: archived ?? this.archived,
|
archived: archived ?? this.archived,
|
||||||
|
groupName: groupName ?? this.groupName,
|
||||||
lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange,
|
lastMessageExchange: lastMessageExchange ?? this.lastMessageExchange,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
|
|
@ -1394,6 +1428,9 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
if (archived.present) {
|
if (archived.present) {
|
||||||
map['archived'] = Variable<bool>(archived.value);
|
map['archived'] = Variable<bool>(archived.value);
|
||||||
}
|
}
|
||||||
|
if (groupName.present) {
|
||||||
|
map['group_name'] = Variable<String>(groupName.value);
|
||||||
|
}
|
||||||
if (lastMessageExchange.present) {
|
if (lastMessageExchange.present) {
|
||||||
map['last_message_exchange'] =
|
map['last_message_exchange'] =
|
||||||
Variable<DateTime>(lastMessageExchange.value);
|
Variable<DateTime>(lastMessageExchange.value);
|
||||||
|
|
@ -1415,6 +1452,7 @@ class GroupsCompanion extends UpdateCompanion<Group> {
|
||||||
..write('isGroupOfTwo: $isGroupOfTwo, ')
|
..write('isGroupOfTwo: $isGroupOfTwo, ')
|
||||||
..write('pinned: $pinned, ')
|
..write('pinned: $pinned, ')
|
||||||
..write('archived: $archived, ')
|
..write('archived: $archived, ')
|
||||||
|
..write('groupName: $groupName, ')
|
||||||
..write('lastMessageExchange: $lastMessageExchange, ')
|
..write('lastMessageExchange: $lastMessageExchange, ')
|
||||||
..write('createdAt: $createdAt, ')
|
..write('createdAt: $createdAt, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
|
|
@ -2231,6 +2269,16 @@ 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 _mediaStoredMeta =
|
||||||
|
const VerificationMeta('mediaStored');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumn<bool> mediaStored = GeneratedColumn<bool>(
|
||||||
|
'media_stored', aliasedName, false,
|
||||||
|
type: DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("media_stored" IN (0, 1))'),
|
||||||
|
defaultValue: const Constant(false));
|
||||||
static const VerificationMeta _downloadTokenMeta =
|
static const VerificationMeta _downloadTokenMeta =
|
||||||
const VerificationMeta('downloadToken');
|
const VerificationMeta('downloadToken');
|
||||||
@override
|
@override
|
||||||
|
|
@ -2323,6 +2371,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
||||||
senderId,
|
senderId,
|
||||||
content,
|
content,
|
||||||
mediaId,
|
mediaId,
|
||||||
|
mediaStored,
|
||||||
downloadToken,
|
downloadToken,
|
||||||
quotesMessageId,
|
quotesMessageId,
|
||||||
isDeletedFromSender,
|
isDeletedFromSender,
|
||||||
|
|
@ -2366,6 +2415,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('media_stored')) {
|
||||||
|
context.handle(
|
||||||
|
_mediaStoredMeta,
|
||||||
|
mediaStored.isAcceptableOrUnknown(
|
||||||
|
data['media_stored']!, _mediaStoredMeta));
|
||||||
|
}
|
||||||
if (data.containsKey('download_token')) {
|
if (data.containsKey('download_token')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_downloadTokenMeta,
|
_downloadTokenMeta,
|
||||||
|
|
@ -2439,6 +2494,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']),
|
||||||
|
mediaStored: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.bool, data['${effectivePrefix}media_stored'])!,
|
||||||
downloadToken: attachedDatabase.typeMapping
|
downloadToken: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.blob, data['${effectivePrefix}download_token']),
|
.read(DriftSqlType.blob, data['${effectivePrefix}download_token']),
|
||||||
quotesMessageId: attachedDatabase.typeMapping.read(
|
quotesMessageId: attachedDatabase.typeMapping.read(
|
||||||
|
|
@ -2474,6 +2531,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 bool mediaStored;
|
||||||
final Uint8List? downloadToken;
|
final Uint8List? downloadToken;
|
||||||
final String? quotesMessageId;
|
final String? quotesMessageId;
|
||||||
final bool isDeletedFromSender;
|
final bool isDeletedFromSender;
|
||||||
|
|
@ -2490,6 +2548,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
this.senderId,
|
this.senderId,
|
||||||
this.content,
|
this.content,
|
||||||
this.mediaId,
|
this.mediaId,
|
||||||
|
required this.mediaStored,
|
||||||
this.downloadToken,
|
this.downloadToken,
|
||||||
this.quotesMessageId,
|
this.quotesMessageId,
|
||||||
required this.isDeletedFromSender,
|
required this.isDeletedFromSender,
|
||||||
|
|
@ -2514,6 +2573,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
map['media_stored'] = Variable<bool>(mediaStored);
|
||||||
if (!nullToAbsent || downloadToken != null) {
|
if (!nullToAbsent || downloadToken != null) {
|
||||||
map['download_token'] = Variable<Uint8List>(downloadToken);
|
map['download_token'] = Variable<Uint8List>(downloadToken);
|
||||||
}
|
}
|
||||||
|
|
@ -2548,6 +2608,7 @@ 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),
|
||||||
|
mediaStored: Value(mediaStored),
|
||||||
downloadToken: downloadToken == null && nullToAbsent
|
downloadToken: downloadToken == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(downloadToken),
|
: Value(downloadToken),
|
||||||
|
|
@ -2578,6 +2639,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']),
|
||||||
|
mediaStored: serializer.fromJson<bool>(json['mediaStored']),
|
||||||
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
downloadToken: serializer.fromJson<Uint8List?>(json['downloadToken']),
|
||||||
quotesMessageId: serializer.fromJson<String?>(json['quotesMessageId']),
|
quotesMessageId: serializer.fromJson<String?>(json['quotesMessageId']),
|
||||||
isDeletedFromSender:
|
isDeletedFromSender:
|
||||||
|
|
@ -2600,6 +2662,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),
|
||||||
|
'mediaStored': serializer.toJson<bool>(mediaStored),
|
||||||
'downloadToken': serializer.toJson<Uint8List?>(downloadToken),
|
'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),
|
||||||
|
|
@ -2619,6 +2682,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(),
|
||||||
|
bool? mediaStored,
|
||||||
Value<Uint8List?> downloadToken = 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,
|
||||||
|
|
@ -2635,6 +2699,7 @@ 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,
|
||||||
|
mediaStored: mediaStored ?? this.mediaStored,
|
||||||
downloadToken:
|
downloadToken:
|
||||||
downloadToken.present ? downloadToken.value : this.downloadToken,
|
downloadToken.present ? downloadToken.value : this.downloadToken,
|
||||||
quotesMessageId: quotesMessageId.present
|
quotesMessageId: quotesMessageId.present
|
||||||
|
|
@ -2656,6 +2721,8 @@ 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,
|
||||||
|
mediaStored:
|
||||||
|
data.mediaStored.present ? data.mediaStored.value : this.mediaStored,
|
||||||
downloadToken: data.downloadToken.present
|
downloadToken: data.downloadToken.present
|
||||||
? data.downloadToken.value
|
? data.downloadToken.value
|
||||||
: this.downloadToken,
|
: this.downloadToken,
|
||||||
|
|
@ -2687,6 +2754,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('mediaStored: $mediaStored, ')
|
||||||
..write('downloadToken: $downloadToken, ')
|
..write('downloadToken: $downloadToken, ')
|
||||||
..write('quotesMessageId: $quotesMessageId, ')
|
..write('quotesMessageId: $quotesMessageId, ')
|
||||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||||
|
|
@ -2708,6 +2776,7 @@ class Message extends DataClass implements Insertable<Message> {
|
||||||
senderId,
|
senderId,
|
||||||
content,
|
content,
|
||||||
mediaId,
|
mediaId,
|
||||||
|
mediaStored,
|
||||||
$driftBlobEquality.hash(downloadToken),
|
$driftBlobEquality.hash(downloadToken),
|
||||||
quotesMessageId,
|
quotesMessageId,
|
||||||
isDeletedFromSender,
|
isDeletedFromSender,
|
||||||
|
|
@ -2727,6 +2796,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 &&
|
||||||
|
other.mediaStored == this.mediaStored &&
|
||||||
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
$driftBlobEquality.equals(other.downloadToken, this.downloadToken) &&
|
||||||
other.quotesMessageId == this.quotesMessageId &&
|
other.quotesMessageId == this.quotesMessageId &&
|
||||||
other.isDeletedFromSender == this.isDeletedFromSender &&
|
other.isDeletedFromSender == this.isDeletedFromSender &&
|
||||||
|
|
@ -2745,6 +2815,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<bool> mediaStored;
|
||||||
final Value<Uint8List?> downloadToken;
|
final Value<Uint8List?> downloadToken;
|
||||||
final Value<String?> quotesMessageId;
|
final Value<String?> quotesMessageId;
|
||||||
final Value<bool> isDeletedFromSender;
|
final Value<bool> isDeletedFromSender;
|
||||||
|
|
@ -2762,6 +2833,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.mediaStored = const Value.absent(),
|
||||||
this.downloadToken = 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(),
|
||||||
|
|
@ -2780,6 +2852,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.mediaStored = const Value.absent(),
|
||||||
this.downloadToken = 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(),
|
||||||
|
|
@ -2798,6 +2871,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<bool>? mediaStored,
|
||||||
Expression<Uint8List>? downloadToken,
|
Expression<Uint8List>? downloadToken,
|
||||||
Expression<String>? quotesMessageId,
|
Expression<String>? quotesMessageId,
|
||||||
Expression<bool>? isDeletedFromSender,
|
Expression<bool>? isDeletedFromSender,
|
||||||
|
|
@ -2816,6 +2890,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 (mediaStored != null) 'media_stored': mediaStored,
|
||||||
if (downloadToken != null) 'download_token': downloadToken,
|
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)
|
||||||
|
|
@ -2837,6 +2912,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<bool>? mediaStored,
|
||||||
Value<Uint8List?>? downloadToken,
|
Value<Uint8List?>? downloadToken,
|
||||||
Value<String?>? quotesMessageId,
|
Value<String?>? quotesMessageId,
|
||||||
Value<bool>? isDeletedFromSender,
|
Value<bool>? isDeletedFromSender,
|
||||||
|
|
@ -2854,6 +2930,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,
|
||||||
|
mediaStored: mediaStored ?? this.mediaStored,
|
||||||
downloadToken: downloadToken ?? this.downloadToken,
|
downloadToken: downloadToken ?? this.downloadToken,
|
||||||
quotesMessageId: quotesMessageId ?? this.quotesMessageId,
|
quotesMessageId: quotesMessageId ?? this.quotesMessageId,
|
||||||
isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender,
|
isDeletedFromSender: isDeletedFromSender ?? this.isDeletedFromSender,
|
||||||
|
|
@ -2886,6 +2963,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 (mediaStored.present) {
|
||||||
|
map['media_stored'] = Variable<bool>(mediaStored.value);
|
||||||
|
}
|
||||||
if (downloadToken.present) {
|
if (downloadToken.present) {
|
||||||
map['download_token'] = Variable<Uint8List>(downloadToken.value);
|
map['download_token'] = Variable<Uint8List>(downloadToken.value);
|
||||||
}
|
}
|
||||||
|
|
@ -2930,6 +3010,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('mediaStored: $mediaStored, ')
|
||||||
..write('downloadToken: $downloadToken, ')
|
..write('downloadToken: $downloadToken, ')
|
||||||
..write('quotesMessageId: $quotesMessageId, ')
|
..write('quotesMessageId: $quotesMessageId, ')
|
||||||
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
..write('isDeletedFromSender: $isDeletedFromSender, ')
|
||||||
|
|
@ -6863,6 +6944,7 @@ typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
||||||
required bool isGroupOfTwo,
|
required bool isGroupOfTwo,
|
||||||
Value<bool> pinned,
|
Value<bool> pinned,
|
||||||
Value<bool> archived,
|
Value<bool> archived,
|
||||||
|
required String groupName,
|
||||||
Value<DateTime> lastMessageExchange,
|
Value<DateTime> lastMessageExchange,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
|
|
@ -6873,6 +6955,7 @@ typedef $$GroupsTableUpdateCompanionBuilder = GroupsCompanion Function({
|
||||||
Value<bool> isGroupOfTwo,
|
Value<bool> isGroupOfTwo,
|
||||||
Value<bool> pinned,
|
Value<bool> pinned,
|
||||||
Value<bool> archived,
|
Value<bool> archived,
|
||||||
|
Value<String> groupName,
|
||||||
Value<DateTime> lastMessageExchange,
|
Value<DateTime> lastMessageExchange,
|
||||||
Value<DateTime> createdAt,
|
Value<DateTime> createdAt,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
|
|
@ -6921,6 +7004,9 @@ class $$GroupsTableFilterComposer extends Composer<_$TwonlyDB, $GroupsTable> {
|
||||||
ColumnFilters<bool> get archived => $composableBuilder(
|
ColumnFilters<bool> get archived => $composableBuilder(
|
||||||
column: $table.archived, builder: (column) => ColumnFilters(column));
|
column: $table.archived, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
ColumnFilters<String> get groupName => $composableBuilder(
|
||||||
|
column: $table.groupName, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<DateTime> get lastMessageExchange => $composableBuilder(
|
ColumnFilters<DateTime> get lastMessageExchange => $composableBuilder(
|
||||||
column: $table.lastMessageExchange,
|
column: $table.lastMessageExchange,
|
||||||
builder: (column) => ColumnFilters(column));
|
builder: (column) => ColumnFilters(column));
|
||||||
|
|
@ -6975,6 +7061,9 @@ class $$GroupsTableOrderingComposer extends Composer<_$TwonlyDB, $GroupsTable> {
|
||||||
ColumnOrderings<bool> get archived => $composableBuilder(
|
ColumnOrderings<bool> get archived => $composableBuilder(
|
||||||
column: $table.archived, builder: (column) => ColumnOrderings(column));
|
column: $table.archived, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<String> get groupName => $composableBuilder(
|
||||||
|
column: $table.groupName, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get lastMessageExchange => $composableBuilder(
|
ColumnOrderings<DateTime> get lastMessageExchange => $composableBuilder(
|
||||||
column: $table.lastMessageExchange,
|
column: $table.lastMessageExchange,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
@ -7007,6 +7096,9 @@ class $$GroupsTableAnnotationComposer
|
||||||
GeneratedColumn<bool> get archived =>
|
GeneratedColumn<bool> get archived =>
|
||||||
$composableBuilder(column: $table.archived, builder: (column) => column);
|
$composableBuilder(column: $table.archived, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumn<String> get groupName =>
|
||||||
|
$composableBuilder(column: $table.groupName, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get lastMessageExchange => $composableBuilder(
|
GeneratedColumn<DateTime> get lastMessageExchange => $composableBuilder(
|
||||||
column: $table.lastMessageExchange, builder: (column) => column);
|
column: $table.lastMessageExchange, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -7063,6 +7155,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
||||||
Value<bool> isGroupOfTwo = const Value.absent(),
|
Value<bool> isGroupOfTwo = const Value.absent(),
|
||||||
Value<bool> pinned = const Value.absent(),
|
Value<bool> pinned = const Value.absent(),
|
||||||
Value<bool> archived = const Value.absent(),
|
Value<bool> archived = const Value.absent(),
|
||||||
|
Value<String> groupName = const Value.absent(),
|
||||||
Value<DateTime> lastMessageExchange = const Value.absent(),
|
Value<DateTime> lastMessageExchange = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
|
|
@ -7073,6 +7166,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
||||||
isGroupOfTwo: isGroupOfTwo,
|
isGroupOfTwo: isGroupOfTwo,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
archived: archived,
|
archived: archived,
|
||||||
|
groupName: groupName,
|
||||||
lastMessageExchange: lastMessageExchange,
|
lastMessageExchange: lastMessageExchange,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
|
|
@ -7083,6 +7177,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
||||||
required bool isGroupOfTwo,
|
required bool isGroupOfTwo,
|
||||||
Value<bool> pinned = const Value.absent(),
|
Value<bool> pinned = const Value.absent(),
|
||||||
Value<bool> archived = const Value.absent(),
|
Value<bool> archived = const Value.absent(),
|
||||||
|
required String groupName,
|
||||||
Value<DateTime> lastMessageExchange = const Value.absent(),
|
Value<DateTime> lastMessageExchange = const Value.absent(),
|
||||||
Value<DateTime> createdAt = const Value.absent(),
|
Value<DateTime> createdAt = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
|
|
@ -7093,6 +7188,7 @@ class $$GroupsTableTableManager extends RootTableManager<
|
||||||
isGroupOfTwo: isGroupOfTwo,
|
isGroupOfTwo: isGroupOfTwo,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
archived: archived,
|
archived: archived,
|
||||||
|
groupName: groupName,
|
||||||
lastMessageExchange: lastMessageExchange,
|
lastMessageExchange: lastMessageExchange,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
|
|
@ -7556,6 +7652,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
|
||||||
Value<int?> senderId,
|
Value<int?> senderId,
|
||||||
Value<String?> content,
|
Value<String?> content,
|
||||||
Value<String?> mediaId,
|
Value<String?> mediaId,
|
||||||
|
Value<bool> mediaStored,
|
||||||
Value<Uint8List?> downloadToken,
|
Value<Uint8List?> downloadToken,
|
||||||
Value<String?> quotesMessageId,
|
Value<String?> quotesMessageId,
|
||||||
Value<bool> isDeletedFromSender,
|
Value<bool> isDeletedFromSender,
|
||||||
|
|
@ -7574,6 +7671,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
|
||||||
Value<int?> senderId,
|
Value<int?> senderId,
|
||||||
Value<String?> content,
|
Value<String?> content,
|
||||||
Value<String?> mediaId,
|
Value<String?> mediaId,
|
||||||
|
Value<bool> mediaStored,
|
||||||
Value<Uint8List?> downloadToken,
|
Value<Uint8List?> downloadToken,
|
||||||
Value<String?> quotesMessageId,
|
Value<String?> quotesMessageId,
|
||||||
Value<bool> isDeletedFromSender,
|
Value<bool> isDeletedFromSender,
|
||||||
|
|
@ -7716,6 +7814,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<bool> get mediaStored => $composableBuilder(
|
||||||
|
column: $table.mediaStored, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
ColumnFilters<Uint8List> get downloadToken => $composableBuilder(
|
ColumnFilters<Uint8List> get downloadToken => $composableBuilder(
|
||||||
column: $table.downloadToken, builder: (column) => ColumnFilters(column));
|
column: $table.downloadToken, builder: (column) => ColumnFilters(column));
|
||||||
|
|
||||||
|
|
@ -7904,6 +8005,9 @@ 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<bool> get mediaStored => $composableBuilder(
|
||||||
|
column: $table.mediaStored, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<Uint8List> get downloadToken => $composableBuilder(
|
ColumnOrderings<Uint8List> get downloadToken => $composableBuilder(
|
||||||
column: $table.downloadToken,
|
column: $table.downloadToken,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
@ -8030,6 +8134,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<bool> get mediaStored => $composableBuilder(
|
||||||
|
column: $table.mediaStored, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<Uint8List> get downloadToken => $composableBuilder(
|
GeneratedColumn<Uint8List> get downloadToken => $composableBuilder(
|
||||||
column: $table.downloadToken, builder: (column) => column);
|
column: $table.downloadToken, builder: (column) => column);
|
||||||
|
|
||||||
|
|
@ -8236,6 +8343,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<bool> mediaStored = const Value.absent(),
|
||||||
Value<Uint8List?> downloadToken = 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(),
|
||||||
|
|
@ -8254,6 +8362,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
senderId: senderId,
|
senderId: senderId,
|
||||||
content: content,
|
content: content,
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
|
mediaStored: mediaStored,
|
||||||
downloadToken: downloadToken,
|
downloadToken: downloadToken,
|
||||||
quotesMessageId: quotesMessageId,
|
quotesMessageId: quotesMessageId,
|
||||||
isDeletedFromSender: isDeletedFromSender,
|
isDeletedFromSender: isDeletedFromSender,
|
||||||
|
|
@ -8272,6 +8381,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<bool> mediaStored = const Value.absent(),
|
||||||
Value<Uint8List?> downloadToken = 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(),
|
||||||
|
|
@ -8290,6 +8400,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
||||||
senderId: senderId,
|
senderId: senderId,
|
||||||
content: content,
|
content: content,
|
||||||
mediaId: mediaId,
|
mediaId: mediaId,
|
||||||
|
mediaStored: mediaStored,
|
||||||
downloadToken: downloadToken,
|
downloadToken: downloadToken,
|
||||||
quotesMessageId: quotesMessageId,
|
quotesMessageId: quotesMessageId,
|
||||||
isDeletedFromSender: isDeletedFromSender,
|
isDeletedFromSender: isDeletedFromSender,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ 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/mediafiles/download.service.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/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';
|
||||||
|
|
@ -94,7 +93,6 @@ class ApiService {
|
||||||
if (!globalIsAppInBackground) {
|
if (!globalIsAppInBackground) {
|
||||||
unawaited(retransmitRawBytes());
|
unawaited(retransmitRawBytes());
|
||||||
unawaited(tryTransmitMessages());
|
unawaited(tryTransmitMessages());
|
||||||
unawaited(retryMediaUpload(false));
|
|
||||||
unawaited(tryDownloadAllMediaFiles());
|
unawaited(tryDownloadAllMediaFiles());
|
||||||
unawaited(notifyContactsAboutProfileChange());
|
unawaited(notifyContactsAboutProfileChange());
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.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/api/mediafiles/download.service.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/mediafiles/upload.service.dart';
|
||||||
|
import 'package:twonly/src/services/mediafiles/mediafile.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';
|
||||||
|
|
||||||
|
|
@ -48,3 +53,59 @@ Future<void> initFileDownloader() async {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
|
final mediaId = update.task.taskId.replaceAll('upload_', '');
|
||||||
|
final media = await twonlyDB.mediaFilesDao.getMediaFileById(mediaId);
|
||||||
|
|
||||||
|
if (media == null) {
|
||||||
|
Log.error(
|
||||||
|
'Got an upload task but no upload media in the media upload database',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.status == TaskStatus.complete) {
|
||||||
|
if (update.responseStatusCode == 200) {
|
||||||
|
Log.info('Upload of ${media.mediaId} success!');
|
||||||
|
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
media.mediaId,
|
||||||
|
const MediaFilesCompanion(
|
||||||
|
uploadState: Value(UploadState.uploaded),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await twonlyDB.messagesDao.updateMessagesByMediaId(
|
||||||
|
media.mediaId,
|
||||||
|
const MessagesCompanion(
|
||||||
|
ackByServer: Value(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.error(
|
||||||
|
'Got HTTP error ${update.responseStatusCode} for $mediaId',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (update.responseStatusCode == 429) {
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaId,
|
||||||
|
const MediaFilesCompanion(
|
||||||
|
uploadState: Value(UploadState.uploadLimitReached),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.info(
|
||||||
|
'Background upload failed for $mediaId with status ${update.status}. Trying again.',
|
||||||
|
);
|
||||||
|
|
||||||
|
final mediaService = await MediaFileService.fromMedia(media);
|
||||||
|
|
||||||
|
await mediaService.setUploadState(UploadState.uploading);
|
||||||
|
// In all other cases just try the upload again...
|
||||||
|
await startBackgroundMediaUpload(mediaService);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,124 +1,21 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
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';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:mutex/mutex.dart';
|
|
||||||
import 'package:path/path.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/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/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/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.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';
|
|
||||||
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:video_compress/video_compress.dart';
|
|
||||||
|
|
||||||
/// States:
|
|
||||||
/// when user recorded an video
|
|
||||||
/// 1. Compress video
|
|
||||||
/// when user clicked the send button (direct send) or share with
|
|
||||||
/// 2. Encrypt media files
|
|
||||||
/// 3. Upload media files
|
|
||||||
/// click send button
|
|
||||||
/// 4. Finalize upload by websocket -> get download tokens
|
|
||||||
/// 5. Send all users the message
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
// }
|
|
||||||
|
|
||||||
final lockingHandleMediaFile = Mutex();
|
|
||||||
Future<void> retryMediaUpload(bool appRestarted, {int maxRetries = 3}) async {
|
|
||||||
if (maxRetries == 0) {
|
|
||||||
Log.error('retried media upload 3 times. abort retrying');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final retry = await lockingHandleMediaFile.protect<bool>(() async {
|
|
||||||
final mediaFiles = await twonlyDB.mediaUploadsDao.getMediaUploadsForRetry();
|
|
||||||
if (mediaFiles.isEmpty) {
|
|
||||||
return checkForFailedUploads();
|
|
||||||
}
|
|
||||||
Log.info('re uploading ${mediaFiles.length} media files.');
|
|
||||||
for (final mediaFile in mediaFiles) {
|
|
||||||
if (mediaFile.messageIds == null || mediaFile.metadata == null) {
|
|
||||||
if (appRestarted) {
|
|
||||||
/// When the app got restarted and the messageIds or the metadata is not
|
|
||||||
/// set then the app was closed before the images was send.
|
|
||||||
await twonlyDB.mediaUploadsDao
|
|
||||||
.deleteMediaUpload(mediaFile.mediaUploadId);
|
|
||||||
Log.info(
|
|
||||||
'upload can be removed, the finalized function was never called...',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaFile.state == UploadState.readyToUpload) {
|
|
||||||
await handleNextMediaUploadSteps(mediaFile.mediaUploadId);
|
|
||||||
} else {
|
|
||||||
await handlePreProcessingState(mediaFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
if (retry) {
|
|
||||||
await retryMediaUpload(false, maxRetries: maxRetries - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<MediaFileService?> initializeMediaUpload(
|
Future<MediaFileService?> initializeMediaUpload(
|
||||||
MediaType type,
|
MediaType type,
|
||||||
|
|
@ -141,78 +38,10 @@ Future<MediaFileService?> initializeMediaUpload(
|
||||||
return MediaFileService.fromMedia(mediaFile);
|
return MediaFileService.fromMedia(mediaFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handlePreProcessingState(MediaUpload media) async {
|
Future<void> insertMediaFileInMessagesTable(
|
||||||
try {
|
|
||||||
final imageHandler = readSendMediaFile(media.mediaUploadId, 'png');
|
|
||||||
final videoHandler = compressVideoIfExists(media.mediaUploadId);
|
|
||||||
await encryptMediaFiles(
|
|
||||||
media.mediaUploadId,
|
|
||||||
imageHandler,
|
|
||||||
videoHandler,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('${media.mediaUploadId} got error in pre processing: $e');
|
|
||||||
await handleUploadError(media);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> encryptMediaFiles(
|
|
||||||
int mediaUploadId,
|
|
||||||
Future<Uint8List> imageHandler,
|
|
||||||
Future<bool>? videoHandler,
|
|
||||||
) async {
|
|
||||||
Log.info('$mediaUploadId encrypting files');
|
|
||||||
var dataToEncrypt = await imageHandler;
|
|
||||||
|
|
||||||
/// if there is a video wait until it is finished with compression
|
|
||||||
if (videoHandler != null) {
|
|
||||||
if (await videoHandler) {
|
|
||||||
final compressedVideo = await readSendMediaFile(mediaUploadId, 'mp4');
|
|
||||||
dataToEncrypt = combineUint8Lists(dataToEncrypt, compressedVideo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final state = MediaEncryptionData();
|
|
||||||
|
|
||||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
|
||||||
|
|
||||||
state
|
|
||||||
..encryptionKey = secretKey.bytes
|
|
||||||
..encryptionNonce = chacha20.newNonce();
|
|
||||||
|
|
||||||
final secretBox = await chacha20.encrypt(
|
|
||||||
dataToEncrypt,
|
|
||||||
secretKey: secretKey,
|
|
||||||
nonce: state.encryptionNonce,
|
|
||||||
);
|
|
||||||
|
|
||||||
state
|
|
||||||
..encryptionMac = secretBox.mac.bytes
|
|
||||||
..sha2Hash = (await Sha256().hash(secretBox.cipherText)).bytes;
|
|
||||||
|
|
||||||
final encryptedBytes = Uint8List.fromList(secretBox.cipherText);
|
|
||||||
await writeSendMediaFile(
|
|
||||||
mediaUploadId,
|
|
||||||
'encrypted',
|
|
||||||
encryptedBytes,
|
|
||||||
);
|
|
||||||
|
|
||||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
|
||||||
mediaUploadId,
|
|
||||||
MediaUploadsCompanion(
|
|
||||||
state: const Value(UploadState.readyToUpload),
|
|
||||||
encryptionData: Value(state),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
unawaited(handleNextMediaUploadSteps(mediaUploadId));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> finalizeUpload(
|
|
||||||
MediaFileService mediaService,
|
MediaFileService mediaService,
|
||||||
List<String> groupIds,
|
List<String> groupIds,
|
||||||
) async {
|
) async {
|
||||||
final messageIds = <Message>[];
|
|
||||||
|
|
||||||
for (final groupId in groupIds) {
|
for (final groupId in groupIds) {
|
||||||
final message = await twonlyDB.messagesDao.insertMessage(
|
final message = await twonlyDB.messagesDao.insertMessage(
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
|
|
@ -221,7 +50,6 @@ Future<void> finalizeUpload(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
messageIds.add(message);
|
|
||||||
// de-archive contact when sending a new message
|
// de-archive contact when sending a new message
|
||||||
await twonlyDB.groupsDao.updateGroup(
|
await twonlyDB.groupsDao.updateGroup(
|
||||||
message.groupId,
|
message.groupId,
|
||||||
|
|
@ -234,233 +62,131 @@ Future<void> finalizeUpload(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unawaited(handleNextMediaUploadSteps(mediaService.mediaFile.mediaId));
|
unawaited(startBackgroundMediaUpload(mediaService));
|
||||||
}
|
}
|
||||||
|
|
||||||
final lockingHandleNextMediaUploadStep = Mutex();
|
Future<void> startBackgroundMediaUpload(MediaFileService mediaService) async {
|
||||||
Future<void> handleNextMediaUploadSteps(String mediaUploadId) async {
|
if (mediaService.mediaFile.uploadState == UploadState.initialized) {
|
||||||
await lockingHandleNextMediaUploadStep.protect(() async {
|
await mediaService.setUploadState(UploadState.preprocessing);
|
||||||
final mediaUpload = await twonlyDB.mediaUploadsDao
|
if (!mediaService.tempPath.existsSync()) {
|
||||||
.getMediaUploadById(mediaUploadId)
|
await mediaService.compressMedia();
|
||||||
.getSingleOrNull();
|
|
||||||
|
|
||||||
if (mediaUpload == null) return false;
|
|
||||||
if (mediaUpload.state == UploadState.receiverNotified ||
|
|
||||||
mediaUpload.state == UploadState.uploadTaskStarted) {
|
|
||||||
/// Upload done and all users are notified :)
|
|
||||||
Log.info('$mediaUploadId is already done');
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
/// Stage 1: media files are not yet encrypted...
|
|
||||||
if (mediaUpload.encryptionData == null) {
|
|
||||||
// when set this function will be called again by encryptAndPreUploadMediaFiles...
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaUpload.messageIds == null || mediaUpload.metadata == null) {
|
if (!mediaService.encryptedPath.existsSync()) {
|
||||||
/// the finalize function was not called yet...
|
await _encryptMediaFiles(mediaService);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await handleMediaUpload(mediaUpload);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('Non recoverable error while sending media file: $e');
|
|
||||||
await handleUploadError(mediaUpload);
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
});
|
if (!mediaService.uploadRequestPath.existsSync()) {
|
||||||
|
await _createUploadRequest(mediaService);
|
||||||
|
}
|
||||||
|
await mediaService.setUploadState(UploadState.uploading);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaService.mediaFile.uploadState == UploadState.uploading) {
|
||||||
|
await _uploadUploadRequest(mediaService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
Future<void> _encryptMediaFiles(MediaFileService mediaService) async {
|
||||||
/// -- private functions --
|
/// if there is a video wait until it is finished with compression
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
|
|
||||||
Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
final dataToEncrypt = await mediaService.tempPath.readAsBytes();
|
||||||
var failed = false;
|
|
||||||
final mediaUploadId = int.parse(update.task.taskId.replaceAll('upload_', ''));
|
|
||||||
|
|
||||||
final media = await twonlyDB.mediaUploadsDao
|
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||||
.getMediaUploadById(mediaUploadId)
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (media == null) {
|
|
||||||
Log.error(
|
|
||||||
'Got an upload task but no upload media in the media upload database',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (update.status == TaskStatus.failed ||
|
|
||||||
update.status == TaskStatus.canceled) {
|
|
||||||
Log.error('Upload failed: ${update.status}');
|
|
||||||
failed = true;
|
|
||||||
} else if (update.status == TaskStatus.complete) {
|
|
||||||
if (update.responseStatusCode == 200) {
|
|
||||||
await handleUploadSuccess(media);
|
|
||||||
return;
|
|
||||||
} else if (update.responseStatusCode != null) {
|
|
||||||
if (update.responseStatusCode! >= 400 &&
|
|
||||||
update.responseStatusCode! < 500) {
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
Log.error(
|
|
||||||
'Got error while uploading: ${update.responseStatusCode}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (failed) {
|
final secretBox = await chacha20.encrypt(
|
||||||
for (final messageId in media.messageIds!) {
|
dataToEncrypt,
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
secretKey: SecretKey(mediaService.mediaFile.encryptionKey!),
|
||||||
messageId,
|
nonce: mediaService.mediaFile.encryptionNonce,
|
||||||
const MessagesCompanion(
|
|
||||||
acknowledgeByServer: Value(true),
|
|
||||||
errorWhileSending: Value(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.info(
|
|
||||||
'Status update for ${update.task.taskId} with status ${update.status}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> handleUploadSuccess(MediaUpload media) async {
|
|
||||||
Log.info('Upload of ${media.mediaUploadId} success!');
|
|
||||||
currentUploadTasks.remove(media.mediaUploadId);
|
|
||||||
|
|
||||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
|
||||||
media.mediaUploadId,
|
|
||||||
const MediaUploadsCompanion(
|
|
||||||
state: Value(UploadState.receiverNotified),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final messageId in media.messageIds!) {
|
await mediaService.setEncryptedMac(Uint8List.fromList(secretBox.mac.bytes));
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
|
||||||
messageId,
|
mediaService.encryptedPath
|
||||||
const MessagesCompanion(
|
.writeAsBytesSync(Uint8List.fromList(secretBox.cipherText));
|
||||||
acknowledgeByServer: Value(true),
|
|
||||||
errorWhileSending: Value(false),
|
await mediaService.setUploadState(UploadState.uploading);
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleUploadError(MediaUpload mediaUpload) async {
|
Future<void> _createUploadRequest(MediaFileService media) async {
|
||||||
// if the messageIds are already there notify the user about this error...
|
|
||||||
if (mediaUpload.messageIds != null) {
|
|
||||||
for (final messageId in mediaUpload.messageIds!) {
|
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
|
||||||
messageId,
|
|
||||||
const MessagesCompanion(
|
|
||||||
errorWhileSending: Value(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await twonlyDB.mediaUploadsDao.deleteMediaUpload(mediaUpload.mediaUploadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> handleMediaUpload(MediaUpload media) async {
|
|
||||||
final bytesToUpload =
|
|
||||||
await readSendMediaFile(media.mediaUploadId, 'encrypted');
|
|
||||||
|
|
||||||
if (media.messageIds == null) return;
|
|
||||||
|
|
||||||
final messageIds = media.messageIds!;
|
|
||||||
|
|
||||||
final downloadTokens = <Uint8List>[];
|
final downloadTokens = <Uint8List>[];
|
||||||
|
|
||||||
final messagesOnSuccess = <TextMessage>[];
|
final messagesOnSuccess = <TextMessage>[];
|
||||||
|
|
||||||
for (var i = 0; i < messageIds.length; i++) {
|
final messages =
|
||||||
final message = await twonlyDB.messagesDao
|
await twonlyDB.messagesDao.getMessagesByMediaId(media.mediaFile.mediaId);
|
||||||
.getMessageByMessageId(messageIds[i])
|
|
||||||
.getSingleOrNull();
|
|
||||||
if (message == null) continue;
|
|
||||||
|
|
||||||
if (message.downloadState == DownloadState.downloaded) {
|
for (final message in messages) {
|
||||||
// only upload message which are not yet uploaded (or in case of an error re-uploaded)
|
final groupMembers =
|
||||||
continue;
|
await twonlyDB.groupsDao.getGroupMembers(message.groupId);
|
||||||
}
|
for (final groupMember in groupMembers) {
|
||||||
|
/// only send the upload to the users
|
||||||
|
if (media.mediaFile.reuploadRequestedBy != null) {
|
||||||
|
if (!media.mediaFile.reuploadRequestedBy!
|
||||||
|
.contains(groupMember.contactId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final downloadToken = getRandomUint8List(32);
|
await twonlyDB.contactsDao.incFlameCounter(
|
||||||
|
groupMember.contactId,
|
||||||
final msg = MessageJson(
|
false,
|
||||||
kind: MessageKind.media,
|
message.createdAt,
|
||||||
messageSenderId: messageIds[i],
|
|
||||||
content: MediaMessageContent(
|
|
||||||
downloadToken: downloadToken,
|
|
||||||
maxShowTime: media.metadata!.maxShowTime,
|
|
||||||
isRealTwonly: media.metadata!.isRealTwonly,
|
|
||||||
isVideo: media.metadata!.isVideo,
|
|
||||||
mirrorVideo: media.metadata!.mirrorVideo,
|
|
||||||
encryptionKey: media.encryptionData!.encryptionKey,
|
|
||||||
encryptionMac: media.encryptionData!.encryptionMac,
|
|
||||||
encryptionNonce: media.encryptionData!.encryptionNonce,
|
|
||||||
),
|
|
||||||
timestamp: media.metadata!.messageSendAt,
|
|
||||||
);
|
|
||||||
|
|
||||||
final plaintextContent = Uint8List.fromList(
|
|
||||||
gzip.encode(utf8.encode(jsonEncode(msg.toJson()))),
|
|
||||||
);
|
|
||||||
|
|
||||||
final contact = await twonlyDB.contactsDao
|
|
||||||
.getContactByUserId(message.contactId)
|
|
||||||
.getSingleOrNull();
|
|
||||||
|
|
||||||
if (contact == null || contact.deleted) {
|
|
||||||
Log.warn(
|
|
||||||
'Contact deleted ${message.contactId} or not found in database.',
|
|
||||||
);
|
);
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
|
||||||
message.messageId,
|
final downloadToken = getRandomUint8List(32);
|
||||||
const MessagesCompanion(errorWhileSending: Value(true)),
|
|
||||||
|
var type = EncryptedContent_Media_Type.IMAGE;
|
||||||
|
if (media.mediaFile.type == MediaType.video) {
|
||||||
|
type = EncryptedContent_Media_Type.VIDEO;
|
||||||
|
} else if (media.mediaFile.type == MediaType.gif) {
|
||||||
|
type = EncryptedContent_Media_Type.GIF;
|
||||||
|
}
|
||||||
|
|
||||||
|
final notEncryptedContent = EncryptedContent(
|
||||||
|
media: EncryptedContent_Media(
|
||||||
|
senderMessageId: message.messageId,
|
||||||
|
type: type,
|
||||||
|
requiresAuthentication: media.mediaFile.requiresAuthentication,
|
||||||
|
timestamp: Int64(message.createdAt.millisecondsSinceEpoch),
|
||||||
|
downloadToken: media.mediaFile.downloadToken,
|
||||||
|
encryptionKey: media.mediaFile.encryptionKey,
|
||||||
|
encryptionNonce: media.mediaFile.encryptionNonce,
|
||||||
|
encryptionMac: media.mediaFile.encryptionMac,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
continue;
|
|
||||||
|
if (media.mediaFile.displayLimitInMilliseconds != null) {
|
||||||
|
notEncryptedContent.media.displayLimitInMilliseconds =
|
||||||
|
Int64(media.mediaFile.displayLimitInMilliseconds!);
|
||||||
|
}
|
||||||
|
|
||||||
|
final cipherText = await sendCipherText(
|
||||||
|
groupMember.contactId,
|
||||||
|
notEncryptedContent,
|
||||||
|
onlyReturnEncryptedData: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cipherText == null) {
|
||||||
|
Log.error(
|
||||||
|
'Could not generate ciphertext message for ${groupMember.contactId}');
|
||||||
|
}
|
||||||
|
|
||||||
|
final messageOnSuccess = TextMessage()
|
||||||
|
..body = cipherText!.$1
|
||||||
|
..userId = Int64(groupMember.contactId);
|
||||||
|
|
||||||
|
if (cipherText.$2 != null) {
|
||||||
|
messageOnSuccess.pushData = cipherText.$2!;
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesOnSuccess.add(messageOnSuccess);
|
||||||
|
downloadTokens.add(downloadToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
await twonlyDB.contactsDao.incFlameCounter(
|
|
||||||
message.contactId,
|
|
||||||
false,
|
|
||||||
message.sendAt,
|
|
||||||
);
|
|
||||||
|
|
||||||
final encryptedBytes = await signalEncryptMessage(
|
|
||||||
message.contactId,
|
|
||||||
plaintextContent,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (encryptedBytes == null) continue;
|
|
||||||
|
|
||||||
final messageOnSuccess = TextMessage()
|
|
||||||
..body = encryptedBytes
|
|
||||||
..userId = Int64(message.contactId);
|
|
||||||
|
|
||||||
final pushKind = (media.metadata!.isRealTwonly)
|
|
||||||
? PushKind.twonly
|
|
||||||
: (media.metadata!.isVideo)
|
|
||||||
? PushKind.video
|
|
||||||
: PushKind.image;
|
|
||||||
|
|
||||||
final pushData = await getPushData(
|
|
||||||
message.contactId,
|
|
||||||
PushNotification(
|
|
||||||
messageId: Int64(message.messageId),
|
|
||||||
kind: pushKind,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (pushData != null) {
|
|
||||||
messageOnSuccess.pushData = pushData.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesOnSuccess.add(messageOnSuccess);
|
|
||||||
downloadTokens.add(downloadToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final bytesToUpload = await media.encryptedPath.readAsBytes();
|
||||||
|
|
||||||
final uploadRequest = UploadRequest(
|
final uploadRequest = UploadRequest(
|
||||||
messagesOnSuccess: messagesOnSuccess,
|
messagesOnSuccess: messagesOnSuccess,
|
||||||
downloadTokens: downloadTokens,
|
downloadTokens: downloadTokens,
|
||||||
|
|
@ -469,6 +195,10 @@ Future<void> handleMediaUpload(MediaUpload media) async {
|
||||||
|
|
||||||
final uploadRequestBytes = uploadRequest.writeToBuffer();
|
final uploadRequestBytes = uploadRequest.writeToBuffer();
|
||||||
|
|
||||||
|
await media.uploadRequestPath.writeAsBytes(uploadRequestBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _uploadUploadRequest(MediaFileService media) async {
|
||||||
final apiAuthTokenRaw = await const FlutterSecureStorage()
|
final apiAuthTokenRaw = await const FlutterSecureStorage()
|
||||||
.read(key: SecureStorageKeys.apiAuthToken);
|
.read(key: SecureStorageKeys.apiAuthToken);
|
||||||
if (apiAuthTokenRaw == null) {
|
if (apiAuthTokenRaw == null) {
|
||||||
|
|
@ -477,108 +207,27 @@ Future<void> handleMediaUpload(MediaUpload media) async {
|
||||||
}
|
}
|
||||||
final apiAuthToken = uint8ListToHex(base64Decode(apiAuthTokenRaw));
|
final apiAuthToken = uint8ListToHex(base64Decode(apiAuthTokenRaw));
|
||||||
|
|
||||||
final uploadRequestFile = await writeSendMediaFile(
|
|
||||||
media.mediaUploadId,
|
|
||||||
'upload',
|
|
||||||
uploadRequestBytes,
|
|
||||||
);
|
|
||||||
|
|
||||||
final apiUrl =
|
final apiUrl =
|
||||||
'http${apiService.apiSecure}://${apiService.apiHost}/api/upload';
|
'http${apiService.apiSecure}://${apiService.apiHost}/api/upload';
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
Log.info('Starting upload from ${media.mediaUploadId}');
|
Log.info('Starting upload from ${media.mediaFile.mediaId}');
|
||||||
|
|
||||||
final task = UploadTask.fromFile(
|
final task = UploadTask.fromFile(
|
||||||
taskId: 'upload_${media.mediaUploadId}',
|
taskId: 'upload_${media.mediaFile.mediaId}',
|
||||||
displayName: (media.metadata?.isVideo ?? false) ? 'image' : 'video',
|
displayName: media.mediaFile.type.name,
|
||||||
file: uploadRequestFile,
|
file: media.uploadRequestPath,
|
||||||
url: apiUrl,
|
url: apiUrl,
|
||||||
priority: 0,
|
priority: 0,
|
||||||
retries: 10,
|
retries: 10,
|
||||||
headers: {
|
headers: {
|
||||||
'x-twonly-auth-token': apiAuthToken,
|
'x-twonly-auth-token': apiAuthToken,
|
||||||
},
|
},
|
||||||
);
|
|
||||||
|
|
||||||
currentUploadTasks[media.mediaUploadId] = task;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await uploadFileFast(media, uploadRequestBytes, apiUrl, apiAuthToken);
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('Fast upload failed: $e. Using slow method directly.');
|
|
||||||
await enqueueUploadTask(media.mediaUploadId);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Log.error('Exception during upload: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<int, UploadTask> currentUploadTasks = {};
|
|
||||||
|
|
||||||
Future<void> enqueueUploadTask(int mediaUploadId) async {
|
|
||||||
if (currentUploadTasks[mediaUploadId] == null) {
|
|
||||||
Log.info('could not enqueue upload task: $mediaUploadId');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.info('Enqueue upload task: $mediaUploadId');
|
|
||||||
|
|
||||||
await FileDownloader().enqueue(currentUploadTasks[mediaUploadId]!);
|
|
||||||
currentUploadTasks.remove(mediaUploadId);
|
|
||||||
|
|
||||||
await twonlyDB.mediaUploadsDao.updateMediaUpload(
|
|
||||||
mediaUploadId,
|
|
||||||
const MediaUploadsCompanion(
|
|
||||||
state: Value(UploadState.uploadTaskStarted),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> handleUploadWhenAppGoesBackground() async {
|
|
||||||
if (currentUploadTasks.keys.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.info('App goes into background. Enqueue uploads to the background.');
|
|
||||||
final keys = currentUploadTasks.keys.toList();
|
|
||||||
for (final key in keys) {
|
|
||||||
await enqueueUploadTask(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> uploadFileFast(
|
|
||||||
MediaUpload media,
|
|
||||||
Uint8List uploadRequestFile,
|
|
||||||
String apiUrl,
|
|
||||||
String apiAuthToken,
|
|
||||||
) async {
|
|
||||||
final requestMultipart = http.MultipartRequest(
|
|
||||||
'POST',
|
|
||||||
Uri.parse(apiUrl),
|
|
||||||
);
|
|
||||||
requestMultipart.headers['x-twonly-auth-token'] = apiAuthToken;
|
|
||||||
|
|
||||||
requestMultipart.files.add(
|
|
||||||
http.MultipartFile.fromBytes(
|
|
||||||
'file',
|
|
||||||
uploadRequestFile,
|
|
||||||
filename: 'upload',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final response = await requestMultipart.send();
|
Log.info('Enqueue upload task: ${task.taskId}');
|
||||||
if (response.statusCode == 200) {
|
|
||||||
Log.info('Upload successful!');
|
await FileDownloader().enqueue(task);
|
||||||
await handleUploadSuccess(media);
|
|
||||||
return;
|
await media.setUploadState(UploadState.backgroundUploadTaskStarted);
|
||||||
} 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}');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,18 +30,20 @@ Future<void> tryTransmitMessages() async {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tryToSendCompleteMessage({
|
// When the ackByServerAt is set this value is written in the receipted
|
||||||
|
Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
String? receiptId,
|
String? receiptId,
|
||||||
Receipt? receipt,
|
Receipt? receipt,
|
||||||
bool reupload = false,
|
bool reupload = false,
|
||||||
|
bool onlyReturnEncryptedData = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
if (receiptId == null && receipt == null) return;
|
if (receiptId == null && receipt == null) return null;
|
||||||
if (receipt == null) {
|
if (receipt == null) {
|
||||||
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
||||||
if (receipt == null) {
|
if (receipt == null) {
|
||||||
Log.error('Receipt $receiptId not found.');
|
Log.error('Receipt $receiptId not found.');
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
receiptId = receipt.receiptId;
|
receiptId = receipt.receiptId;
|
||||||
|
|
@ -55,9 +57,9 @@ Future<void> tryToSendCompleteMessage({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receipt.ackByServerAt != null) {
|
if (!onlyReturnEncryptedData && receipt.ackByServerAt != null) {
|
||||||
Log.error('$receiptId message already uploaded!');
|
Log.error('$receiptId message already uploaded!');
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info('Uploading $receiptId (Message to ${receipt.contactId})');
|
Log.info('Uploading $receiptId (Message to ${receipt.contactId})');
|
||||||
|
|
@ -86,7 +88,7 @@ Future<void> tryToSendCompleteMessage({
|
||||||
);
|
);
|
||||||
if (cipherText == null) {
|
if (cipherText == null) {
|
||||||
Log.error('Could not encrypt the message. Aborting and trying again.');
|
Log.error('Could not encrypt the message. Aborting and trying again.');
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
message.encryptedContent = cipherText.serialize();
|
message.encryptedContent = cipherText.serialize();
|
||||||
switch (cipherText.getType()) {
|
switch (cipherText.getType()) {
|
||||||
|
|
@ -96,10 +98,14 @@ Future<void> tryToSendCompleteMessage({
|
||||||
message.type = pb.Message_Type.CIPHERTEXT;
|
message.type = pb.Message_Type.CIPHERTEXT;
|
||||||
default:
|
default:
|
||||||
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
Log.error('Invalid ciphertext type: ${cipherText.getType()}.');
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onlyReturnEncryptedData) {
|
||||||
|
return (message.writeToBuffer(), pushData);
|
||||||
|
}
|
||||||
|
|
||||||
final resp = await apiService.sendTextMessage(
|
final resp = await apiService.sendTextMessage(
|
||||||
receipt.contactId,
|
receipt.contactId,
|
||||||
message.writeToBuffer(),
|
message.writeToBuffer(),
|
||||||
|
|
@ -114,7 +120,7 @@ Future<void> tryToSendCompleteMessage({
|
||||||
receipt.contactId,
|
receipt.contactId,
|
||||||
const ContactsCompanion(deleted: Value(true)),
|
const ContactsCompanion(deleted: Value(true)),
|
||||||
);
|
);
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,12 +155,14 @@ Future<void> tryToSendCompleteMessage({
|
||||||
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
await twonlyDB.receiptsDao.deleteReceipt(receipt.receiptId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendCipherText(
|
Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
int contactId,
|
int contactId,
|
||||||
pb.EncryptedContent encryptedContent,
|
pb.EncryptedContent encryptedContent, {
|
||||||
) async {
|
bool onlyReturnEncryptedData = false,
|
||||||
|
}) async {
|
||||||
final response = pb.Message()
|
final response = pb.Message()
|
||||||
..type = pb.Message_Type.CIPHERTEXT
|
..type = pb.Message_Type.CIPHERTEXT
|
||||||
..encryptedContent = encryptedContent.writeToBuffer();
|
..encryptedContent = encryptedContent.writeToBuffer();
|
||||||
|
|
@ -163,12 +171,17 @@ Future<void> sendCipherText(
|
||||||
ReceiptsCompanion(
|
ReceiptsCompanion(
|
||||||
contactId: Value(contactId),
|
contactId: Value(contactId),
|
||||||
message: Value(response.writeToBuffer()),
|
message: Value(response.writeToBuffer()),
|
||||||
|
ackByServerAt: Value(onlyReturnEncryptedData ? DateTime.now() : null),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (receipt != null) {
|
if (receipt != null) {
|
||||||
await tryToSendCompleteMessage(receipt: receipt);
|
return tryToSendCompleteMessage(
|
||||||
|
receipt: receipt,
|
||||||
|
onlyReturnEncryptedData: onlyReturnEncryptedData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> notifyContactAboutOpeningMessage(
|
Future<void> notifyContactAboutOpeningMessage(
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,26 @@ class MediaFileService {
|
||||||
await updateFromDB();
|
await updateFromDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setUploadState(UploadState uploadState) async {
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(
|
||||||
|
uploadState: Value(uploadState),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await updateFromDB();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setEncryptedMac(Uint8List encryptionMac) async {
|
||||||
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
|
mediaFile.mediaId,
|
||||||
|
MediaFilesCompanion(
|
||||||
|
encryptionMac: Value(encryptionMac),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await updateFromDB();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> setRequiresAuth(bool requiresAuthentication) async {
|
Future<void> setRequiresAuth(bool requiresAuthentication) async {
|
||||||
await twonlyDB.mediaFilesDao.updateMedia(
|
await twonlyDB.mediaFilesDao.updateMedia(
|
||||||
mediaFile.mediaId,
|
mediaFile.mediaId,
|
||||||
|
|
@ -98,7 +118,8 @@ class MediaFileService {
|
||||||
encryptedPath,
|
encryptedPath,
|
||||||
originalPath,
|
originalPath,
|
||||||
storedPath,
|
storedPath,
|
||||||
thumbnailPath
|
thumbnailPath,
|
||||||
|
uploadRequestPath
|
||||||
];
|
];
|
||||||
|
|
||||||
for (final path in pathsToRemove) {
|
for (final path in pathsToRemove) {
|
||||||
|
|
@ -160,6 +181,10 @@ class MediaFileService {
|
||||||
'tmp',
|
'tmp',
|
||||||
namePrefix: '.encrypted',
|
namePrefix: '.encrypted',
|
||||||
);
|
);
|
||||||
|
File get uploadRequestPath => _buildFilePath(
|
||||||
|
'tmp',
|
||||||
|
namePrefix: '.upload',
|
||||||
|
);
|
||||||
File get originalPath => _buildFilePath(
|
File get originalPath => _buildFilePath(
|
||||||
'tmp',
|
'tmp',
|
||||||
namePrefix: '.original',
|
namePrefix: '.original',
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,9 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.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/client/generated/push_notification.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/push_notification.pbenum.dart';
|
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.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/views/camera/share_image_editor_view.dart'
|
|
||||||
show gMediaShowInfinite;
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
@ -34,7 +31,7 @@ Future<void> customLocalPushNotification(String title, String msg) async {
|
||||||
);
|
);
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
gMediaShowInfinite + Random.secure().nextInt(9999),
|
Random.secure().nextInt(9999),
|
||||||
title,
|
title,
|
||||||
msg,
|
msg,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ 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/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/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
Future<void> enableTwonlySafe(String password) async {
|
Future<void> enableTwonlySafe(String password) async {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@ 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/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/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/settings/backup/backup.view.dart';
|
import 'package:twonly/src/views/settings/backup/backup.view.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,22 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:math';
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.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';
|
|
||||||
|
|
||||||
class SaveToGalleryButton extends StatefulWidget {
|
class SaveToGalleryButton extends StatefulWidget {
|
||||||
const SaveToGalleryButton({
|
const SaveToGalleryButton({
|
||||||
required this.getMergedImage,
|
required this.getMergedImage,
|
||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
required this.displayButtonLabel,
|
required this.displayButtonLabel,
|
||||||
|
required this.mediaService,
|
||||||
super.key,
|
super.key,
|
||||||
this.mediaUploadId,
|
|
||||||
this.videoFilePath,
|
|
||||||
});
|
});
|
||||||
final Future<Uint8List?> Function() getMergedImage;
|
final Future<Uint8List?> Function() getMergedImage;
|
||||||
final bool displayButtonLabel;
|
final bool displayButtonLabel;
|
||||||
final File? videoFilePath;
|
final MediaFileService mediaService;
|
||||||
final int? mediaUploadId;
|
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -54,44 +46,20 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
});
|
});
|
||||||
|
|
||||||
String? res;
|
String? res;
|
||||||
var memoryPath = await getMediaBaseFilePath('memories');
|
|
||||||
|
|
||||||
if (widget.mediaUploadId != null) {
|
final storedMediaPath = widget.mediaService.storedPath;
|
||||||
memoryPath = join(memoryPath, '${widget.mediaUploadId!}');
|
|
||||||
} else {
|
|
||||||
final random = Random();
|
|
||||||
final token = uint8ListToHex(
|
|
||||||
List<int>.generate(32, (i) => random.nextInt(256)),
|
|
||||||
);
|
|
||||||
memoryPath = join(memoryPath, token);
|
|
||||||
}
|
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return;
|
|
||||||
final storeToGallery = user.storeMediaFilesInGallery;
|
|
||||||
|
|
||||||
if (widget.videoFilePath != null) {
|
final storeToGallery = gUser.storeMediaFilesInGallery;
|
||||||
memoryPath += '.mp4';
|
|
||||||
await File(widget.videoFilePath!.path).copy(memoryPath);
|
await widget.mediaService.storeMediaFile();
|
||||||
unawaited(createThumbnailsForVideo(File(memoryPath)));
|
|
||||||
if (storeToGallery) {
|
if (storeToGallery) {
|
||||||
res = await saveVideoToGallery(widget.videoFilePath!.path);
|
res = await saveVideoToGallery(storedMediaPath.path);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final imageBytes = await widget.getMergedImage();
|
|
||||||
if (imageBytes == null || !mounted) return;
|
|
||||||
final webPImageBytes =
|
|
||||||
await FlutterImageCompress.compressWithList(
|
|
||||||
format: CompressFormat.webp,
|
|
||||||
imageBytes,
|
|
||||||
quality: 100,
|
|
||||||
);
|
|
||||||
memoryPath += '.png';
|
|
||||||
await File(memoryPath).writeAsBytes(webPImageBytes);
|
|
||||||
unawaited(createThumbnailsForImage(File(memoryPath)));
|
|
||||||
if (storeToGallery) {
|
|
||||||
res = await saveImageToGallery(imageBytes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await widget.mediaService.compressMedia();
|
||||||
|
await widget.mediaService.createThumbnail();
|
||||||
|
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_imageSaved = true;
|
_imageSaved = true;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
@ -9,7 +8,6 @@ import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
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/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/api/mediafiles/upload.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
|
|
@ -92,9 +90,9 @@ class CameraPreviewControllerView extends StatelessWidget {
|
||||||
required this.selectedCameraDetails,
|
required this.selectedCameraDetails,
|
||||||
required this.screenshotController,
|
required this.screenshotController,
|
||||||
super.key,
|
super.key,
|
||||||
this.sendTo,
|
this.sendToGroup,
|
||||||
});
|
});
|
||||||
final Contact? sendTo;
|
final Group? sendToGroup;
|
||||||
final Future<CameraController?> Function(
|
final Future<CameraController?> Function(
|
||||||
int sCameraId,
|
int sCameraId,
|
||||||
bool init,
|
bool init,
|
||||||
|
|
@ -112,7 +110,7 @@ class CameraPreviewControllerView extends StatelessWidget {
|
||||||
if (snap.hasData) {
|
if (snap.hasData) {
|
||||||
if (snap.data!) {
|
if (snap.data!) {
|
||||||
return CameraPreviewView(
|
return CameraPreviewView(
|
||||||
sendTo: sendTo,
|
sendToGroup: sendToGroup,
|
||||||
selectCamera: selectCamera,
|
selectCamera: selectCamera,
|
||||||
cameraController: cameraController,
|
cameraController: cameraController,
|
||||||
selectedCameraDetails: selectedCameraDetails,
|
selectedCameraDetails: selectedCameraDetails,
|
||||||
|
|
@ -141,9 +139,9 @@ class CameraPreviewView extends StatefulWidget {
|
||||||
required this.selectedCameraDetails,
|
required this.selectedCameraDetails,
|
||||||
required this.screenshotController,
|
required this.screenshotController,
|
||||||
super.key,
|
super.key,
|
||||||
this.sendTo,
|
this.sendToGroup,
|
||||||
});
|
});
|
||||||
final Contact? sendTo;
|
final Group? sendToGroup;
|
||||||
final Future<CameraController?> Function(
|
final Future<CameraController?> Function(
|
||||||
int sCameraId,
|
int sCameraId,
|
||||||
bool init,
|
bool init,
|
||||||
|
|
@ -328,7 +326,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
||||||
imageBytesFuture: imageBytes,
|
imageBytesFuture: imageBytes,
|
||||||
sharedFromGallery: sharedFromGallery,
|
sharedFromGallery: sharedFromGallery,
|
||||||
sendTo: widget.sendTo,
|
sendToGroup: widget.sendToGroup,
|
||||||
mediaFileService: mediaFileService,
|
mediaFileService: mediaFileService,
|
||||||
),
|
),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
|
@ -347,7 +345,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (!mounted) return true;
|
if (!mounted) return true;
|
||||||
// shouldReturn is null when the user used the back button
|
// shouldReturn is null when the user used the back button
|
||||||
if (shouldReturn != null && shouldReturn) {
|
if (shouldReturn != null && shouldReturn) {
|
||||||
if (widget.sendTo == null) {
|
if (widget.sendToGroup == null) {
|
||||||
globalUpdateOfHomeViewPageIndex(0);
|
globalUpdateOfHomeViewPageIndex(0);
|
||||||
} else {
|
} else {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
@ -476,19 +474,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File? videoPathFile;
|
|
||||||
final videoPath = await widget.cameraController?.stopVideoRecording();
|
final videoPath = await widget.cameraController?.stopVideoRecording();
|
||||||
if (videoPath != null) {
|
if (videoPath == null) return;
|
||||||
if (Platform.isAndroid) {
|
|
||||||
// see https://github.com/flutter/flutter/issues/148335
|
|
||||||
await File(videoPath.path).rename('${videoPath.path}.mp4');
|
|
||||||
videoPathFile = File('${videoPath.path}.mp4');
|
|
||||||
} else {
|
|
||||||
videoPathFile = File(videoPath.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await widget.cameraController?.pausePreview();
|
await widget.cameraController?.pausePreview();
|
||||||
if (await pushMediaEditor(null, videoPathFile)) {
|
if (await pushMediaEditor(null, File(videoPath.path))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} on CameraException catch (e) {
|
} on CameraException catch (e) {
|
||||||
|
|
@ -568,9 +557,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!sharePreviewIsShown &&
|
if (!sharePreviewIsShown &&
|
||||||
widget.sendTo != null &&
|
widget.sendToGroup != null &&
|
||||||
!isVideoRecording)
|
!isVideoRecording)
|
||||||
SendToWidget(sendTo: getContactDisplayName(widget.sendTo!)),
|
SendToWidget(sendTo: widget.sendToGroup!.groupName),
|
||||||
if (!sharePreviewIsShown && !isVideoRecording)
|
if (!sharePreviewIsShown && !isVideoRecording)
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 5,
|
right: 5,
|
||||||
|
|
@ -722,7 +711,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
videoRecordingStarted: videoRecordingStarted,
|
videoRecordingStarted: videoRecordingStarted,
|
||||||
maxVideoRecordingTime: maxVideoRecordingTime,
|
maxVideoRecordingTime: maxVideoRecordingTime,
|
||||||
),
|
),
|
||||||
if (!sharePreviewIsShown && widget.sendTo != null)
|
if (!sharePreviewIsShown && widget.sendToGroup != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 5,
|
left: 5,
|
||||||
top: 10,
|
top: 10,
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import 'package:twonly/src/views/camera/camera_preview_components/camera_preview
|
||||||
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
|
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
|
||||||
|
|
||||||
class CameraSendToView extends StatefulWidget {
|
class CameraSendToView extends StatefulWidget {
|
||||||
const CameraSendToView(this.sendTo, {super.key});
|
const CameraSendToView(this.sendToGroup, {super.key});
|
||||||
final Contact sendTo;
|
final Group sendToGroup;
|
||||||
@override
|
@override
|
||||||
State<CameraSendToView> createState() => CameraSendToViewState();
|
State<CameraSendToView> createState() => CameraSendToViewState();
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
),
|
),
|
||||||
CameraPreviewControllerView(
|
CameraPreviewControllerView(
|
||||||
selectCamera: selectCamera,
|
selectCamera: selectCamera,
|
||||||
sendTo: widget.sendTo,
|
sendToGroup: widget.sendToGroup,
|
||||||
cameraController: cameraController,
|
cameraController: cameraController,
|
||||||
selectedCameraDetails: selectedCameraDetails,
|
selectedCameraDetails: selectedCameraDetails,
|
||||||
screenshotController: screenshotController,
|
screenshotController: screenshotController,
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,11 @@
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
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: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/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/api/mediafiles/upload.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||||
|
|
@ -28,25 +23,22 @@ import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_view.dart';
|
import 'package:twonly/src/views/camera/share_image_view.dart';
|
||||||
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
import 'package:twonly/src/views/components/media_view_sizing.dart';
|
||||||
import 'package:twonly/src/views/components/notification_badge.dart';
|
import 'package:twonly/src/views/components/notification_badge.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
List<Layer> layers = [];
|
List<Layer> layers = [];
|
||||||
List<Layer> undoLayers = [];
|
List<Layer> undoLayers = [];
|
||||||
List<Layer> removedLayers = [];
|
List<Layer> removedLayers = [];
|
||||||
|
|
||||||
const gMediaShowInfinite = 999999;
|
|
||||||
|
|
||||||
class ShareImageEditorView extends StatefulWidget {
|
class ShareImageEditorView extends StatefulWidget {
|
||||||
const ShareImageEditorView({
|
const ShareImageEditorView({
|
||||||
required this.sharedFromGallery,
|
required this.sharedFromGallery,
|
||||||
required this.mediaFileService,
|
required this.mediaFileService,
|
||||||
super.key,
|
super.key,
|
||||||
this.imageBytesFuture,
|
this.imageBytesFuture,
|
||||||
this.sendTo,
|
this.sendToGroup,
|
||||||
});
|
});
|
||||||
final Future<Uint8List?>? imageBytesFuture;
|
final Future<Uint8List?>? imageBytesFuture;
|
||||||
final Group? sendTo;
|
final Group? sendToGroup;
|
||||||
final bool sharedFromGallery;
|
final bool sharedFromGallery;
|
||||||
final MediaFileService mediaFileService;
|
final MediaFileService mediaFileService;
|
||||||
@override
|
@override
|
||||||
|
|
@ -66,9 +58,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
ImageItem currentImage = ImageItem();
|
ImageItem currentImage = ImageItem();
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
||||||
/// Media upload variables
|
|
||||||
Future<bool>? videoUploadHandler;
|
|
||||||
|
|
||||||
MediaFileService get mediaService => widget.mediaFileService;
|
MediaFileService get mediaService => widget.mediaFileService;
|
||||||
MediaFile get media => widget.mediaFileService.mediaFile;
|
MediaFile get media => widget.mediaFileService.mediaFile;
|
||||||
|
|
||||||
|
|
@ -78,8 +67,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
|
|
||||||
layers.add(FilterLayerData());
|
layers.add(FilterLayerData());
|
||||||
|
|
||||||
if (widget.sendTo != null) {
|
if (widget.sendToGroup != null) {
|
||||||
selectedGroupIds.add(widget.sendTo!.groupId);
|
selectedGroupIds.add(widget.sendToGroup!.groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widget.imageBytesFuture != null) {
|
if (widget.imageBytesFuture != null) {
|
||||||
|
|
@ -284,17 +273,18 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pushShareImageView() async {
|
Future<void> pushShareImageView() async {
|
||||||
final imageBytes = storeImageAsOriginal();
|
final mediaStoreFuture =
|
||||||
|
(media.type == MediaType.image) ? storeImageAsOriginal() : null;
|
||||||
|
|
||||||
await videoController?.pause();
|
await videoController?.pause();
|
||||||
if (isDisposed || !mounted) return;
|
if (isDisposed || !mounted) return;
|
||||||
final wasSend = await Navigator.push(
|
final wasSend = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ShareImageView(
|
builder: (context) => ShareImageView(
|
||||||
imageBytesFuture: imageBytes,
|
selectedGroupIds: selectedGroupIds,
|
||||||
selectedUserIds: selectedGroupIds,
|
updateSelectedGroupIds: updateSelectedGroupIds,
|
||||||
updateStatus: updateSelectedGroupIds,
|
mediaStoreFuture: mediaStoreFuture,
|
||||||
videoUploadHandler: videoUploadHandler,
|
|
||||||
mediaFileService: mediaService,
|
mediaFileService: mediaService,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -306,11 +296,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> storeImageAsOriginal() async {
|
Future<Uint8List?> getEditedImageBytes() async {
|
||||||
if (layers.length == 1) {
|
if (layers.length == 1) {
|
||||||
if (layers.first is BackgroundLayerData) {
|
if (layers.first is BackgroundLayerData) {
|
||||||
final image = (layers.first as BackgroundLayerData).image.bytes;
|
final image = (layers.first as BackgroundLayerData).image.bytes;
|
||||||
mediaService.originalPath.writeAsBytesSync(image);
|
return image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,24 +314,31 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
);
|
);
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
Log.error('screenshotController did not return image bytes');
|
Log.error('screenshotController did not return image bytes');
|
||||||
return;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
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(() {});
|
||||||
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> storeImageAsOriginal() async {
|
||||||
|
final imageBytes = await getEditedImageBytes();
|
||||||
|
if (imageBytes == null) return false;
|
||||||
|
mediaService.originalPath.writeAsBytesSync(imageBytes);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
|
Future<void> loadImage(Future<Uint8List?> imageBytesFuture) async {
|
||||||
|
|
@ -377,14 +374,10 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
// first finalize the upload
|
// Insert media file into the messages database and start uploading process in the background
|
||||||
await finalizeUpload(mediaService, [widget.sendTo!.groupId]);
|
await insertMediaFileInMessagesTable(
|
||||||
|
mediaService,
|
||||||
/// then call the upload process in the background
|
[widget.sendToGroup!.groupId],
|
||||||
await encryptMediaFiles(
|
|
||||||
mediaUploadId!,
|
|
||||||
imageHandler,
|
|
||||||
videoUploadHandler,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|
@ -434,14 +427,13 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SaveToGalleryButton(
|
SaveToGalleryButton(
|
||||||
getMergedImage: getMergedImage,
|
getMergedImage: getEditedImageBytes,
|
||||||
mediaUploadId: mediaUploadId,
|
mediaService: mediaService,
|
||||||
videoFilePath: widget.videoFilePath,
|
displayButtonLabel: widget.sendToGroup == null,
|
||||||
displayButtonLabel: widget.sendTo == null,
|
|
||||||
isLoading: loadingImage,
|
isLoading: loadingImage,
|
||||||
),
|
),
|
||||||
if (widget.sendTo != null) const SizedBox(width: 10),
|
if (widget.sendToGroup != null) const SizedBox(width: 10),
|
||||||
if (widget.sendTo != null)
|
if (widget.sendToGroup != null)
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
iconColor: Theme.of(context).colorScheme.primary,
|
iconColor: Theme.of(context).colorScheme.primary,
|
||||||
|
|
@ -451,7 +443,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
onPressed: pushShareImageView,
|
onPressed: pushShareImageView,
|
||||||
child: const FaIcon(FontAwesomeIcons.userPlus),
|
child: const FaIcon(FontAwesomeIcons.userPlus),
|
||||||
),
|
),
|
||||||
SizedBox(width: widget.sendTo == null ? 20 : 10),
|
SizedBox(width: widget.sendToGroup == null ? 20 : 10),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
icon: sendingOrLoadingImage
|
icon: sendingOrLoadingImage
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
|
|
@ -467,7 +459,8 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (sendingOrLoadingImage) return;
|
if (sendingOrLoadingImage) return;
|
||||||
if (widget.sendTo == null) return pushShareImageView();
|
if (widget.sendToGroup == null)
|
||||||
|
return pushShareImageView();
|
||||||
await sendImageToSinglePerson();
|
await sendImageToSinglePerson();
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
|
|
@ -479,9 +472,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
(widget.sendTo == null)
|
(widget.sendToGroup == null)
|
||||||
? context.lang.shareImagedEditorShareWith
|
? context.lang.shareImagedEditorShareWith
|
||||||
: getContactDisplayName(widget.sendTo!),
|
: widget.sendToGroup!.groupName,
|
||||||
style: const TextStyle(fontSize: 17),
|
style: const TextStyle(fontSize: 17),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/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/services/api/mediafiles/upload.service.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
|
|
@ -21,19 +22,15 @@ 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.selectedGroupIds,
|
||||||
required this.selectedUserIds,
|
required this.updateSelectedGroupIds,
|
||||||
required this.updateStatus,
|
required this.mediaStoreFuture,
|
||||||
required this.videoUploadHandler,
|
|
||||||
required this.mediaFileService,
|
required this.mediaFileService,
|
||||||
super.key,
|
super.key,
|
||||||
this.enableVideoAudio,
|
|
||||||
});
|
});
|
||||||
final Future<Uint8List?> imageBytesFuture;
|
final HashSet<String> selectedGroupIds;
|
||||||
final HashSet<int> selectedUserIds;
|
final void Function(String, bool) updateSelectedGroupIds;
|
||||||
final bool? enableVideoAudio;
|
final Future<bool>? mediaStoreFuture;
|
||||||
final void Function(int, bool) updateStatus;
|
|
||||||
final Future<bool>? videoUploadHandler;
|
|
||||||
final MediaFileService mediaFileService;
|
final MediaFileService mediaFileService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -69,17 +66,11 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
imageBytes = await widget.imageBytesFuture;
|
if (widget.mediaStoreFuture != null) {
|
||||||
if (imageBytes != null) {
|
await widget.mediaStoreFuture;
|
||||||
final imageHandler =
|
|
||||||
addOrModifyImageToUpload(widget.mediaUploadId, imageBytes!);
|
|
||||||
// start with the pre upload of the media file...
|
|
||||||
await encryptMediaFiles(
|
|
||||||
widget.mediaUploadId,
|
|
||||||
imageHandler,
|
|
||||||
widget.videoUploadHandler,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
await widget.mediaFileService.setUploadState(UploadState.preprocessing);
|
||||||
|
unawaited(startBackgroundMediaUpload(widget.mediaFileService));
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue