mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
fixing memories
This commit is contained in:
parent
1c154e6c67
commit
84828cd820
21 changed files with 347 additions and 413 deletions
|
|
@ -6,7 +6,6 @@ import 'package:twonly/globals.dart';
|
|||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/components/app_outdated.dart';
|
||||
import 'package:twonly/src/views/home.view.dart';
|
||||
|
|
@ -68,8 +67,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
await setUserPlan();
|
||||
await apiService.connect(force: true);
|
||||
await apiService.listenToNetworkChanges();
|
||||
// call this function so invalid media files are get purged
|
||||
await retryMediaUpload(true);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -84,7 +81,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
} else if (state == AppLifecycleState.paused) {
|
||||
wasPaused = true;
|
||||
globalIsAppInBackground = true;
|
||||
unawaited(handleUploadWhenAppGoesBackground());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,14 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> deleteMediaFile(String mediaId) async {
|
||||
await (delete(mediaFiles)
|
||||
..where(
|
||||
(t) => t.mediaId.equals(mediaId),
|
||||
))
|
||||
.go();
|
||||
}
|
||||
|
||||
Future<void> updateMedia(
|
||||
String mediaId,
|
||||
MediaFilesCompanion updates,
|
||||
|
|
@ -57,4 +65,8 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
|||
..where((t) => t.downloadState.equals(DownloadState.pending.name)))
|
||||
.get();
|
||||
}
|
||||
|
||||
Stream<List<MediaFile>> watchAllStoredMediaFiles() {
|
||||
return (select(mediaFiles)..where((t) => t.stored.equals(true))).watch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
|
|||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
|
||||
enum MessageType { media, text }
|
||||
|
||||
@DataClassName('Message')
|
||||
class Messages extends Table {
|
||||
TextColumn get groupId =>
|
||||
|
|
@ -14,9 +16,12 @@ class Messages extends Table {
|
|||
IntColumn get senderId =>
|
||||
integer().nullable().references(Contacts, #userId)();
|
||||
|
||||
TextColumn get type => textEnum<MessageType>()();
|
||||
|
||||
TextColumn get content => text().nullable()();
|
||||
TextColumn get mediaId =>
|
||||
text().nullable().references(MediaFiles, #mediaId)();
|
||||
TextColumn get mediaId => text()
|
||||
.nullable()
|
||||
.references(MediaFiles, #mediaId, onDelete: KeyAction.cascade)();
|
||||
|
||||
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
|
||||
|
||||
|
|
|
|||
|
|
@ -2254,6 +2254,11 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)'));
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<MessageType, String> type =
|
||||
GeneratedColumn<String>('type', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||
.withConverter<MessageType>($MessagesTable.$convertertype);
|
||||
static const VerificationMeta _contentMeta =
|
||||
const VerificationMeta('content');
|
||||
@override
|
||||
|
|
@ -2268,7 +2273,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES media_files (media_id)'));
|
||||
'REFERENCES media_files (media_id) ON DELETE CASCADE'));
|
||||
static const VerificationMeta _mediaStoredMeta =
|
||||
const VerificationMeta('mediaStored');
|
||||
@override
|
||||
|
|
@ -2327,6 +2332,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
groupId,
|
||||
messageId,
|
||||
senderId,
|
||||
type,
|
||||
content,
|
||||
mediaId,
|
||||
mediaStored,
|
||||
|
|
@ -2415,6 +2421,8 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
.read(DriftSqlType.string, data['${effectivePrefix}message_id'])!,
|
||||
senderId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}sender_id']),
|
||||
type: $MessagesTable.$convertertype.fromSql(attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}type'])!),
|
||||
content: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}content']),
|
||||
mediaId: attachedDatabase.typeMapping
|
||||
|
|
@ -2438,12 +2446,16 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
$MessagesTable createAlias(String alias) {
|
||||
return $MessagesTable(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static JsonTypeConverter2<MessageType, String, String> $convertertype =
|
||||
const EnumNameConverter<MessageType>(MessageType.values);
|
||||
}
|
||||
|
||||
class Message extends DataClass implements Insertable<Message> {
|
||||
final String groupId;
|
||||
final String messageId;
|
||||
final int? senderId;
|
||||
final MessageType type;
|
||||
final String? content;
|
||||
final String? mediaId;
|
||||
final bool mediaStored;
|
||||
|
|
@ -2456,6 +2468,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
{required this.groupId,
|
||||
required this.messageId,
|
||||
this.senderId,
|
||||
required this.type,
|
||||
this.content,
|
||||
this.mediaId,
|
||||
required this.mediaStored,
|
||||
|
|
@ -2472,6 +2485,9 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
if (!nullToAbsent || senderId != null) {
|
||||
map['sender_id'] = Variable<int>(senderId);
|
||||
}
|
||||
{
|
||||
map['type'] = Variable<String>($MessagesTable.$convertertype.toSql(type));
|
||||
}
|
||||
if (!nullToAbsent || content != null) {
|
||||
map['content'] = Variable<String>(content);
|
||||
}
|
||||
|
|
@ -2498,6 +2514,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
senderId: senderId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(senderId),
|
||||
type: Value(type),
|
||||
content: content == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(content),
|
||||
|
|
@ -2524,6 +2541,8 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
groupId: serializer.fromJson<String>(json['groupId']),
|
||||
messageId: serializer.fromJson<String>(json['messageId']),
|
||||
senderId: serializer.fromJson<int?>(json['senderId']),
|
||||
type: $MessagesTable.$convertertype
|
||||
.fromJson(serializer.fromJson<String>(json['type'])),
|
||||
content: serializer.fromJson<String?>(json['content']),
|
||||
mediaId: serializer.fromJson<String?>(json['mediaId']),
|
||||
mediaStored: serializer.fromJson<bool>(json['mediaStored']),
|
||||
|
|
@ -2542,6 +2561,8 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
'groupId': serializer.toJson<String>(groupId),
|
||||
'messageId': serializer.toJson<String>(messageId),
|
||||
'senderId': serializer.toJson<int?>(senderId),
|
||||
'type':
|
||||
serializer.toJson<String>($MessagesTable.$convertertype.toJson(type)),
|
||||
'content': serializer.toJson<String?>(content),
|
||||
'mediaId': serializer.toJson<String?>(mediaId),
|
||||
'mediaStored': serializer.toJson<bool>(mediaStored),
|
||||
|
|
@ -2557,6 +2578,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
{String? groupId,
|
||||
String? messageId,
|
||||
Value<int?> senderId = const Value.absent(),
|
||||
MessageType? type,
|
||||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
bool? mediaStored,
|
||||
|
|
@ -2569,6 +2591,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
groupId: groupId ?? this.groupId,
|
||||
messageId: messageId ?? this.messageId,
|
||||
senderId: senderId.present ? senderId.value : this.senderId,
|
||||
type: type ?? this.type,
|
||||
content: content.present ? content.value : this.content,
|
||||
mediaId: mediaId.present ? mediaId.value : this.mediaId,
|
||||
mediaStored: mediaStored ?? this.mediaStored,
|
||||
|
|
@ -2586,6 +2609,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
groupId: data.groupId.present ? data.groupId.value : this.groupId,
|
||||
messageId: data.messageId.present ? data.messageId.value : this.messageId,
|
||||
senderId: data.senderId.present ? data.senderId.value : this.senderId,
|
||||
type: data.type.present ? data.type.value : this.type,
|
||||
content: data.content.present ? data.content.value : this.content,
|
||||
mediaId: data.mediaId.present ? data.mediaId.value : this.mediaId,
|
||||
mediaStored:
|
||||
|
|
@ -2610,6 +2634,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
..write('groupId: $groupId, ')
|
||||
..write('messageId: $messageId, ')
|
||||
..write('senderId: $senderId, ')
|
||||
..write('type: $type, ')
|
||||
..write('content: $content, ')
|
||||
..write('mediaId: $mediaId, ')
|
||||
..write('mediaStored: $mediaStored, ')
|
||||
|
|
@ -2627,6 +2652,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
groupId,
|
||||
messageId,
|
||||
senderId,
|
||||
type,
|
||||
content,
|
||||
mediaId,
|
||||
mediaStored,
|
||||
|
|
@ -2642,6 +2668,7 @@ class Message extends DataClass implements Insertable<Message> {
|
|||
other.groupId == this.groupId &&
|
||||
other.messageId == this.messageId &&
|
||||
other.senderId == this.senderId &&
|
||||
other.type == this.type &&
|
||||
other.content == this.content &&
|
||||
other.mediaId == this.mediaId &&
|
||||
other.mediaStored == this.mediaStored &&
|
||||
|
|
@ -2656,6 +2683,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
final Value<String> groupId;
|
||||
final Value<String> messageId;
|
||||
final Value<int?> senderId;
|
||||
final Value<MessageType> type;
|
||||
final Value<String?> content;
|
||||
final Value<String?> mediaId;
|
||||
final Value<bool> mediaStored;
|
||||
|
|
@ -2669,6 +2697,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
this.groupId = const Value.absent(),
|
||||
this.messageId = const Value.absent(),
|
||||
this.senderId = const Value.absent(),
|
||||
this.type = const Value.absent(),
|
||||
this.content = const Value.absent(),
|
||||
this.mediaId = const Value.absent(),
|
||||
this.mediaStored = const Value.absent(),
|
||||
|
|
@ -2683,6 +2712,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
required String groupId,
|
||||
this.messageId = const Value.absent(),
|
||||
this.senderId = const Value.absent(),
|
||||
required MessageType type,
|
||||
this.content = const Value.absent(),
|
||||
this.mediaId = const Value.absent(),
|
||||
this.mediaStored = const Value.absent(),
|
||||
|
|
@ -2692,11 +2722,13 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
this.isEdited = const Value.absent(),
|
||||
this.createdAt = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
}) : groupId = Value(groupId);
|
||||
}) : groupId = Value(groupId),
|
||||
type = Value(type);
|
||||
static Insertable<Message> custom({
|
||||
Expression<String>? groupId,
|
||||
Expression<String>? messageId,
|
||||
Expression<int>? senderId,
|
||||
Expression<String>? type,
|
||||
Expression<String>? content,
|
||||
Expression<String>? mediaId,
|
||||
Expression<bool>? mediaStored,
|
||||
|
|
@ -2711,6 +2743,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
if (groupId != null) 'group_id': groupId,
|
||||
if (messageId != null) 'message_id': messageId,
|
||||
if (senderId != null) 'sender_id': senderId,
|
||||
if (type != null) 'type': type,
|
||||
if (content != null) 'content': content,
|
||||
if (mediaId != null) 'media_id': mediaId,
|
||||
if (mediaStored != null) 'media_stored': mediaStored,
|
||||
|
|
@ -2728,6 +2761,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
{Value<String>? groupId,
|
||||
Value<String>? messageId,
|
||||
Value<int?>? senderId,
|
||||
Value<MessageType>? type,
|
||||
Value<String?>? content,
|
||||
Value<String?>? mediaId,
|
||||
Value<bool>? mediaStored,
|
||||
|
|
@ -2741,6 +2775,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
groupId: groupId ?? this.groupId,
|
||||
messageId: messageId ?? this.messageId,
|
||||
senderId: senderId ?? this.senderId,
|
||||
type: type ?? this.type,
|
||||
content: content ?? this.content,
|
||||
mediaId: mediaId ?? this.mediaId,
|
||||
mediaStored: mediaStored ?? this.mediaStored,
|
||||
|
|
@ -2765,6 +2800,10 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
if (senderId.present) {
|
||||
map['sender_id'] = Variable<int>(senderId.value);
|
||||
}
|
||||
if (type.present) {
|
||||
map['type'] =
|
||||
Variable<String>($MessagesTable.$convertertype.toSql(type.value));
|
||||
}
|
||||
if (content.present) {
|
||||
map['content'] = Variable<String>(content.value);
|
||||
}
|
||||
|
|
@ -2801,6 +2840,7 @@ class MessagesCompanion extends UpdateCompanion<Message> {
|
|||
..write('groupId: $groupId, ')
|
||||
..write('messageId: $messageId, ')
|
||||
..write('senderId: $senderId, ')
|
||||
..write('type: $type, ')
|
||||
..write('content: $content, ')
|
||||
..write('mediaId: $mediaId, ')
|
||||
..write('mediaStored: $mediaStored, ')
|
||||
|
|
@ -6149,6 +6189,13 @@ abstract class _$TwonlyDB extends GeneratedDatabase {
|
|||
TableUpdate('messages', kind: UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName('media_files',
|
||||
limitUpdateKind: UpdateKind.delete),
|
||||
result: [
|
||||
TableUpdate('messages', kind: UpdateKind.delete),
|
||||
],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName('messages',
|
||||
limitUpdateKind: UpdateKind.delete),
|
||||
|
|
@ -7817,6 +7864,7 @@ typedef $$MessagesTableCreateCompanionBuilder = MessagesCompanion Function({
|
|||
required String groupId,
|
||||
Value<String> messageId,
|
||||
Value<int?> senderId,
|
||||
required MessageType type,
|
||||
Value<String?> content,
|
||||
Value<String?> mediaId,
|
||||
Value<bool> mediaStored,
|
||||
|
|
@ -7831,6 +7879,7 @@ typedef $$MessagesTableUpdateCompanionBuilder = MessagesCompanion Function({
|
|||
Value<String> groupId,
|
||||
Value<String> messageId,
|
||||
Value<int?> senderId,
|
||||
Value<MessageType> type,
|
||||
Value<String?> content,
|
||||
Value<String?> mediaId,
|
||||
Value<bool> mediaStored,
|
||||
|
|
@ -7984,6 +8033,11 @@ class $$MessagesTableFilterComposer
|
|||
ColumnFilters<String> get messageId => $composableBuilder(
|
||||
column: $table.messageId, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<MessageType, MessageType, String> get type =>
|
||||
$composableBuilder(
|
||||
column: $table.type,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
|
||||
ColumnFilters<String> get content => $composableBuilder(
|
||||
column: $table.content, builder: (column) => ColumnFilters(column));
|
||||
|
||||
|
|
@ -8180,6 +8234,9 @@ class $$MessagesTableOrderingComposer
|
|||
ColumnOrderings<String> get messageId => $composableBuilder(
|
||||
column: $table.messageId, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get type => $composableBuilder(
|
||||
column: $table.type, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get content => $composableBuilder(
|
||||
column: $table.content, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
|
|
@ -8293,6 +8350,9 @@ class $$MessagesTableAnnotationComposer
|
|||
GeneratedColumn<String> get messageId =>
|
||||
$composableBuilder(column: $table.messageId, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<MessageType, String> get type =>
|
||||
$composableBuilder(column: $table.type, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get content =>
|
||||
$composableBuilder(column: $table.content, builder: (column) => column);
|
||||
|
||||
|
|
@ -8510,6 +8570,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
Value<String> groupId = const Value.absent(),
|
||||
Value<String> messageId = const Value.absent(),
|
||||
Value<int?> senderId = const Value.absent(),
|
||||
Value<MessageType> type = const Value.absent(),
|
||||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
Value<bool> mediaStored = const Value.absent(),
|
||||
|
|
@ -8524,6 +8585,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
groupId: groupId,
|
||||
messageId: messageId,
|
||||
senderId: senderId,
|
||||
type: type,
|
||||
content: content,
|
||||
mediaId: mediaId,
|
||||
mediaStored: mediaStored,
|
||||
|
|
@ -8538,6 +8600,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
required String groupId,
|
||||
Value<String> messageId = const Value.absent(),
|
||||
Value<int?> senderId = const Value.absent(),
|
||||
required MessageType type,
|
||||
Value<String?> content = const Value.absent(),
|
||||
Value<String?> mediaId = const Value.absent(),
|
||||
Value<bool> mediaStored = const Value.absent(),
|
||||
|
|
@ -8552,6 +8615,7 @@ class $$MessagesTableTableManager extends RootTableManager<
|
|||
groupId: groupId,
|
||||
messageId: messageId,
|
||||
senderId: senderId,
|
||||
type: type,
|
||||
content: content,
|
||||
mediaId: mediaId,
|
||||
mediaStored: mediaStored,
|
||||
|
|
|
|||
|
|
@ -1,88 +1,30 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/json/message_old.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
|
||||
class MemoryItem {
|
||||
MemoryItem({
|
||||
required this.id,
|
||||
required this.mediaService,
|
||||
required this.messages,
|
||||
required this.date,
|
||||
required this.mirrorVideo,
|
||||
required this.thumbnailPath,
|
||||
this.imagePath,
|
||||
this.videoPath,
|
||||
});
|
||||
final int id;
|
||||
final bool mirrorVideo;
|
||||
final List<Message> messages;
|
||||
final DateTime date;
|
||||
final File thumbnailPath;
|
||||
final File? imagePath;
|
||||
final File? videoPath;
|
||||
final MediaFileService mediaService;
|
||||
|
||||
static Future<Map<int, MemoryItem>> convertFromMessages(
|
||||
static Future<Map<String, MemoryItem>> convertFromMessages(
|
||||
List<Message> messages,
|
||||
) async {
|
||||
final items = <int, MemoryItem>{};
|
||||
final items = <String, MemoryItem>{};
|
||||
for (final message in messages) {
|
||||
final isSend = message.messageOtherId == null;
|
||||
final id = message.mediaUploadId ?? message.messageId;
|
||||
final basePath = await send.getMediaFilePath(
|
||||
id,
|
||||
isSend ? 'send' : 'received',
|
||||
);
|
||||
File? imagePath;
|
||||
late File thumbnailFile;
|
||||
File? videoPath;
|
||||
if (File('$basePath.mp4').existsSync()) {
|
||||
videoPath = File('$basePath.mp4');
|
||||
thumbnailFile = getThumbnailPath(videoPath);
|
||||
if (!thumbnailFile.existsSync()) {
|
||||
await createThumbnailsForVideo(videoPath);
|
||||
}
|
||||
} else if (File('$basePath.png').existsSync()) {
|
||||
imagePath = File('$basePath.png');
|
||||
thumbnailFile = getThumbnailPath(imagePath);
|
||||
if (!thumbnailFile.existsSync()) {
|
||||
await createThumbnailsForImage(imagePath);
|
||||
}
|
||||
} else {
|
||||
if (message.mediaStored) {
|
||||
/// media file was deleted, ... remove the file
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
message.messageId,
|
||||
const MessagesCompanion(
|
||||
mediaStored: Value(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var mirrorVideo = false;
|
||||
if (videoPath != null) {
|
||||
final content = MediaMessageContent.fromJson(
|
||||
jsonDecode(message.contentJson!) as Map,
|
||||
);
|
||||
mirrorVideo = content.mirrorVideo;
|
||||
}
|
||||
if (message.mediaId == null) continue;
|
||||
|
||||
final mediaService = await MediaFileService.fromMediaId(message.mediaId!);
|
||||
if (mediaService == null) continue;
|
||||
|
||||
items
|
||||
.putIfAbsent(
|
||||
id,
|
||||
message.mediaId!,
|
||||
() => MemoryItem(
|
||||
id: id,
|
||||
mediaService: mediaService,
|
||||
messages: [],
|
||||
date: message.sendAt,
|
||||
mirrorVideo: mirrorVideo,
|
||||
thumbnailPath: thumbnailFile,
|
||||
imagePath: imagePath,
|
||||
videoPath: videoPath,
|
||||
),
|
||||
)
|
||||
.messages
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:flutter_image_compress/flutter_image_compress.dart';
|
|||
import 'package:gal/gal.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||
|
|
@ -258,3 +259,28 @@ Uint8List hexToUint8List(String hex) => Uint8List.fromList(
|
|||
(i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16),
|
||||
),
|
||||
);
|
||||
|
||||
PieTheme getPieCanvasTheme(BuildContext context) {
|
||||
return PieTheme(
|
||||
brightness: Theme.of(context).brightness,
|
||||
rightClickShowsMenu: true,
|
||||
radius: 70,
|
||||
buttonTheme: PieButtonTheme(
|
||||
backgroundColor: Theme.of(context).colorScheme.tertiary,
|
||||
iconColor: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
buttonThemeHovered: PieButtonTheme(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
iconColor: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
tooltipPadding: const EdgeInsets.all(20),
|
||||
overlayColor: isDarkMode(context)
|
||||
? const Color.fromARGB(69, 0, 0, 0)
|
||||
: const Color.fromARGB(40, 0, 0, 0),
|
||||
// spacing: 0,
|
||||
tooltipTextStyle: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,14 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
|||
selectedGroupIds.add(widget.sendToGroup!.groupId);
|
||||
}
|
||||
|
||||
if (widget.imageBytesFuture != null) {
|
||||
unawaited(loadImage(widget.imageBytesFuture!));
|
||||
if (widget.mediaFileService.mediaFile.type == MediaType.image) {
|
||||
if (widget.imageBytesFuture != null) {
|
||||
loadImage(widget.imageBytesFuture!);
|
||||
} else {
|
||||
if (widget.mediaFileService.storedPath.existsSync()) {
|
||||
loadImage(widget.mediaFileService.storedPath.readAsBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (media.type == MediaType.video) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/json/userdata.dart';
|
||||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
|
|
@ -28,7 +27,7 @@ import 'package:twonly/src/views/chats/start_new_chat.view.dart';
|
|||
import 'package:twonly/src/views/components/flame.dart';
|
||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/views/components/notification_badge.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.component.dart';
|
||||
import 'package:twonly/src/views/settings/help/changelog.view.dart';
|
||||
import 'package:twonly/src/views/settings/profile/profile.view.dart';
|
||||
import 'package:twonly/src/views/settings/settings_main.view.dart';
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import 'package:twonly/src/views/chats/chat_messages_components/chat_list_entry.
|
|||
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.component.dart';
|
||||
import 'package:twonly/src/views/components/verified_shield.dart';
|
||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
||||
|
|
@ -61,9 +61,9 @@ class ChatItem {
|
|||
|
||||
/// Displays detailed information about a SampleItem.
|
||||
class ChatMessagesView extends StatefulWidget {
|
||||
const ChatMessagesView(this.contact, {super.key});
|
||||
const ChatMessagesView(this.group, {super.key});
|
||||
|
||||
final Contact contact;
|
||||
final Group group;
|
||||
|
||||
@override
|
||||
State<ChatMessagesView> createState() => _ChatMessagesViewState();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import 'package:twonly/src/views/chats/add_new_user.view.dart';
|
|||
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
||||
import 'package:twonly/src/views/components/flame.dart';
|
||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.component.dart';
|
||||
|
||||
class StartNewChatView extends StatefulWidget {
|
||||
const StartNewChatView({super.key});
|
||||
|
|
|
|||
96
lib/src/views/components/group_context_menu.component.dart
Normal file
96
lib/src/views/components/group_context_menu.component.dart
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
||||
|
||||
class GroupContextMenu extends StatefulWidget {
|
||||
const GroupContextMenu({
|
||||
required this.group,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
final Widget child;
|
||||
final Group group;
|
||||
|
||||
@override
|
||||
State<GroupContextMenu> createState() => _GroupContextMenuState();
|
||||
}
|
||||
|
||||
class _GroupContextMenuState extends State<GroupContextMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PieMenu(
|
||||
onPressed: () => (),
|
||||
onToggle: (menuOpen) async {
|
||||
if (menuOpen) {
|
||||
await HapticFeedback.heavyImpact();
|
||||
}
|
||||
},
|
||||
actions: [
|
||||
if (!widget.group.archived)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuArchiveUser),
|
||||
onSelect: () async {
|
||||
const update = GroupsCompanion(archived: Value(true));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.groupsDao
|
||||
.updateGroup(widget.group.groupId, update);
|
||||
}
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.boxArchive),
|
||||
),
|
||||
if (widget.group.archived)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuUndoArchiveUser),
|
||||
onSelect: () async {
|
||||
const update = GroupsCompanion(archived: Value(false));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.groupsDao
|
||||
.updateGroup(widget.group.groupId, update);
|
||||
}
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.boxOpen),
|
||||
),
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuOpenChat),
|
||||
onSelect: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ChatMessagesView(widget.group);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.solidComments),
|
||||
),
|
||||
PieAction(
|
||||
tooltip: Text(
|
||||
widget.group.pinned
|
||||
? context.lang.contextMenuUnpin
|
||||
: context.lang.contextMenuPin,
|
||||
),
|
||||
onSelect: () async {
|
||||
final update = GroupsCompanion(pinned: Value(!widget.group.pinned));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.groupsDao
|
||||
.updateGroup(widget.group.groupId, update);
|
||||
}
|
||||
},
|
||||
child: FaIcon(
|
||||
widget.group.pinned
|
||||
? FontAwesomeIcons.thumbtackSlash
|
||||
: FontAwesomeIcons.thumbtack,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
45
lib/src/views/components/user_context_menu.component.dart
Normal file
45
lib/src/views/components/user_context_menu.component.dart
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||
|
||||
class UserContextMenuBlocked extends StatefulWidget {
|
||||
const UserContextMenuBlocked({
|
||||
required this.contact,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
final Widget child;
|
||||
final Contact contact;
|
||||
|
||||
@override
|
||||
State<UserContextMenuBlocked> createState() => _UserContextMenuBlocked();
|
||||
}
|
||||
|
||||
class _UserContextMenuBlocked extends State<UserContextMenuBlocked> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PieMenu(
|
||||
onPressed: () => (),
|
||||
actions: [
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuUserProfile),
|
||||
onSelect: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ContactView(widget.contact.userId);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.user),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||
|
||||
class UserContextMenu extends StatefulWidget {
|
||||
const UserContextMenu({
|
||||
required this.contact,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
final Widget child;
|
||||
final Contact contact;
|
||||
|
||||
@override
|
||||
State<UserContextMenu> createState() => _UserContextMenuState();
|
||||
}
|
||||
|
||||
class _UserContextMenuState extends State<UserContextMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PieMenu(
|
||||
onPressed: () => (),
|
||||
onToggle: (menuOpen) async {
|
||||
if (menuOpen) {
|
||||
await HapticFeedback.heavyImpact();
|
||||
}
|
||||
},
|
||||
actions: [
|
||||
if (!widget.contact.archived)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuArchiveUser),
|
||||
onSelect: () async {
|
||||
const update = ContactsCompanion(archived: Value(true));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.contactsDao
|
||||
.updateContact(widget.contact.userId, update);
|
||||
}
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.boxArchive),
|
||||
),
|
||||
if (widget.contact.archived)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuUndoArchiveUser),
|
||||
onSelect: () async {
|
||||
const update = ContactsCompanion(archived: Value(false));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.contactsDao
|
||||
.updateContact(widget.contact.userId, update);
|
||||
}
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.boxOpen),
|
||||
),
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuOpenChat),
|
||||
onSelect: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ChatMessagesView(widget.contact);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.solidComments),
|
||||
),
|
||||
PieAction(
|
||||
tooltip: Text(
|
||||
widget.contact.pinned
|
||||
? context.lang.contextMenuUnpin
|
||||
: context.lang.contextMenuPin,
|
||||
),
|
||||
onSelect: () async {
|
||||
final update =
|
||||
ContactsCompanion(pinned: Value(!widget.contact.pinned));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.contactsDao
|
||||
.updateContact(widget.contact.userId, update);
|
||||
}
|
||||
},
|
||||
child: FaIcon(
|
||||
widget.contact.pinned
|
||||
? FontAwesomeIcons.thumbtackSlash
|
||||
: FontAwesomeIcons.thumbtack,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UserContextMenuBlocked extends StatefulWidget {
|
||||
const UserContextMenuBlocked({
|
||||
required this.contact,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
final Widget child;
|
||||
final Contact contact;
|
||||
|
||||
@override
|
||||
State<UserContextMenuBlocked> createState() => _UserContextMenuBlocked();
|
||||
}
|
||||
|
||||
class _UserContextMenuBlocked extends State<UserContextMenuBlocked> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PieMenu(
|
||||
onPressed: () => (),
|
||||
actions: [
|
||||
if (!widget.contact.archived)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuArchiveUser),
|
||||
onSelect: () async {
|
||||
const update = ContactsCompanion(archived: Value(true));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.contactsDao
|
||||
.updateContact(widget.contact.userId, update);
|
||||
}
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.boxArchive),
|
||||
),
|
||||
if (widget.contact.archived)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuUndoArchiveUser),
|
||||
onSelect: () async {
|
||||
const update = ContactsCompanion(archived: Value(false));
|
||||
if (context.mounted) {
|
||||
await twonlyDB.contactsDao
|
||||
.updateContact(widget.contact.userId, update);
|
||||
}
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.boxOpen),
|
||||
),
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.contextMenuUserProfile),
|
||||
onSelect: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return ContactView(widget.contact.userId);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.user),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PieTheme getPieCanvasTheme(BuildContext context) {
|
||||
return PieTheme(
|
||||
brightness: Theme.of(context).brightness,
|
||||
rightClickShowsMenu: true,
|
||||
radius: 70,
|
||||
buttonTheme: PieButtonTheme(
|
||||
backgroundColor: Theme.of(context).colorScheme.tertiary,
|
||||
iconColor: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
buttonThemeHovered: PieButtonTheme(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
iconColor: Theme.of(context).colorScheme.surfaceBright,
|
||||
),
|
||||
tooltipPadding: const EdgeInsets.all(20),
|
||||
overlayColor: isDarkMode(context)
|
||||
? const Color.fromARGB(69, 0, 0, 0)
|
||||
: const Color.fromARGB(40, 0, 0, 0),
|
||||
// spacing: 0,
|
||||
tooltipTextStyle: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -7,11 +7,9 @@ import 'package:video_player/video_player.dart';
|
|||
class VideoPlayerWrapper extends StatefulWidget {
|
||||
const VideoPlayerWrapper({
|
||||
required this.videoPath,
|
||||
required this.mirrorVideo,
|
||||
super.key,
|
||||
});
|
||||
final File videoPath;
|
||||
final bool mirrorVideo;
|
||||
|
||||
@override
|
||||
State<VideoPlayerWrapper> createState() => _VideoPlayerWrapperState();
|
||||
|
|
@ -48,10 +46,7 @@ class _VideoPlayerWrapperState extends State<VideoPlayerWrapper> {
|
|||
child: _controller.value.isInitialized
|
||||
? AspectRatio(
|
||||
aspectRatio: _controller.value.aspectRatio,
|
||||
child: Transform.flip(
|
||||
flipX: widget.mirrorVideo,
|
||||
child: VideoPlayer(_controller),
|
||||
),
|
||||
child: VideoPlayer(_controller),
|
||||
)
|
||||
: const CircularProgressIndicator(), // Show loading indicator while initializing
|
||||
);
|
||||
|
|
|
|||
|
|
@ -163,26 +163,26 @@ class _ContactViewState extends State<ContactView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.eraser,
|
||||
iconSize: 16,
|
||||
text: context.lang.deleteAllContactMessages,
|
||||
onTap: () async {
|
||||
final block = await showAlertDialog(
|
||||
context,
|
||||
context.lang.deleteAllContactMessages,
|
||||
context.lang.deleteAllContactMessagesBody(
|
||||
getContactDisplayName(contact),
|
||||
),
|
||||
);
|
||||
if (block) {
|
||||
if (context.mounted) {
|
||||
await twonlyDB.messagesDao
|
||||
.deleteMessagesByContactId(contact.userId);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
// BetterListTile(
|
||||
// icon: FontAwesomeIcons.eraser,
|
||||
// iconSize: 16,
|
||||
// text: context.lang.deleteAllContactMessages,
|
||||
// onTap: () async {
|
||||
// final block = await showAlertDialog(
|
||||
// context,
|
||||
// context.lang.deleteAllContactMessages,
|
||||
// context.lang.deleteAllContactMessagesBody(
|
||||
// getContactDisplayName(contact),
|
||||
// ),
|
||||
// );
|
||||
// if (block) {
|
||||
// if (context.mounted) {
|
||||
// await twonlyDB.messagesDao
|
||||
// .deleteMessagesByContactId(contact.userId);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// ),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.flag,
|
||||
text: context.lang.reportUser,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
|
||||
import 'package:twonly/src/views/camera/camera_preview_controller_view.dart';
|
||||
import 'package:twonly/src/views/chats/chat_list.view.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||
import 'package:twonly/src/views/memories/memories.view.dart';
|
||||
|
||||
void Function(int) globalUpdateOfHomeViewPageIndex = (a) {};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/memory_item.model.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||
import 'package:twonly/src/services/mediafiles/thumbnail.service.dart';
|
||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
|
||||
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';
|
||||
|
|
@ -24,7 +22,7 @@ class MemoriesViewState extends State<MemoriesView> {
|
|||
List<MemoryItem> galleryItems = [];
|
||||
Map<String, List<int>> orderedByMonth = {};
|
||||
List<String> months = [];
|
||||
StreamSubscription<List<Message>>? messageSub;
|
||||
StreamSubscription<List<MediaFile>>? messageSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -38,76 +36,37 @@ class MemoriesViewState extends State<MemoriesView> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
Future<List<MemoryItem>> loadMemoriesDirectory() async {
|
||||
final directoryPath = await send.getMediaBaseFilePath('memories');
|
||||
final directory = Directory(directoryPath);
|
||||
|
||||
final items = <MemoryItem>[];
|
||||
if (directory.existsSync()) {
|
||||
final files = directory.listSync();
|
||||
|
||||
for (final file in files) {
|
||||
if (file is File) {
|
||||
final fileName = file.uri.pathSegments.last;
|
||||
File? imagePath;
|
||||
File? videoPath;
|
||||
late File thumbnailFile;
|
||||
if (fileName.contains('.thumbnail.')) {
|
||||
continue;
|
||||
}
|
||||
if (fileName.contains('.png')) {
|
||||
imagePath = file;
|
||||
thumbnailFile = file;
|
||||
// if (!await thumbnailFile.exists()) {
|
||||
// await createThumbnailsForImage(imagePath);
|
||||
// }
|
||||
} else if (fileName.contains('.mp4')) {
|
||||
videoPath = file;
|
||||
thumbnailFile = getThumbnailPath(videoPath);
|
||||
if (!thumbnailFile.existsSync()) {
|
||||
await createThumbnailsForVideo(videoPath);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
final creationDate = file.lastModifiedSync();
|
||||
items.add(
|
||||
MemoryItem(
|
||||
id: int.parse(fileName.split('.')[0]),
|
||||
messages: [],
|
||||
date: creationDate,
|
||||
mirrorVideo: false,
|
||||
thumbnailPath: thumbnailFile,
|
||||
imagePath: imagePath,
|
||||
videoPath: videoPath,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
await messageSub?.cancel();
|
||||
final msgStream = twonlyDB.messagesDao.getAllStoredMediaFiles();
|
||||
final msgStream = twonlyDB.mediaFilesDao.watchAllStoredMediaFiles();
|
||||
|
||||
messageSub = msgStream.listen((msgs) async {
|
||||
final items = await MemoryItem.convertFromMessages(msgs);
|
||||
messageSub = msgStream.listen((mediaFiles) async {
|
||||
// Group items by month
|
||||
orderedByMonth = {};
|
||||
months = [];
|
||||
var lastMonth = '';
|
||||
galleryItems = await loadMemoriesDirectory();
|
||||
for (final item in galleryItems) {
|
||||
items.remove(
|
||||
item.id,
|
||||
); // prefer the stored one and not the saved on in the chat....
|
||||
galleryItems = [];
|
||||
final applicationSupportDirectory =
|
||||
await getApplicationSupportDirectory();
|
||||
for (final mediaFile in mediaFiles) {
|
||||
galleryItems.add(
|
||||
MemoryItem(
|
||||
mediaService: MediaFileService(
|
||||
mediaFile,
|
||||
applicationSupportDirectory: applicationSupportDirectory,
|
||||
),
|
||||
messages: [],
|
||||
),
|
||||
);
|
||||
}
|
||||
galleryItems += items.values.toList();
|
||||
galleryItems.sort((a, b) => b.date.compareTo(a.date));
|
||||
galleryItems.sort(
|
||||
(a, b) => b.mediaService.mediaFile.createdAt.compareTo(
|
||||
a.mediaService.mediaFile.createdAt,
|
||||
),
|
||||
);
|
||||
for (var i = 0; i < galleryItems.length; i++) {
|
||||
final month = DateFormat('MMMM yyyy').format(galleryItems[i].date);
|
||||
final month = DateFormat('MMMM yyyy')
|
||||
.format(galleryItems[i].mediaService.mediaFile.createdAt);
|
||||
if (lastMonth != month) {
|
||||
lastMonth = month;
|
||||
months.add(month);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/model/memory_item.model.dart';
|
||||
|
||||
class MemoriesItemThumbnail extends StatefulWidget {
|
||||
|
|
@ -39,11 +40,12 @@ class _MemoriesItemThumbnailState extends State<MemoriesItemThumbnail> {
|
|||
return GestureDetector(
|
||||
onTap: widget.onTap,
|
||||
child: Hero(
|
||||
tag: widget.galleryItem.id.toString(),
|
||||
tag: widget.galleryItem.mediaService.mediaFile.mediaId,
|
||||
child: Stack(
|
||||
children: [
|
||||
Image.file(widget.galleryItem.thumbnailPath),
|
||||
if (widget.galleryItem.videoPath != null)
|
||||
Image.file(widget.galleryItem.mediaService.thumbnailPath),
|
||||
if (widget.galleryItem.mediaService.mediaFile.type ==
|
||||
MediaType.video)
|
||||
const Positioned.fill(
|
||||
child: Center(
|
||||
child: FaIcon(FontAwesomeIcons.circlePlay),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:photo_view/photo_view_gallery.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
import 'package:twonly/src/model/memory_item.model.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.service.dart'
|
||||
as received;
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart' as send;
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||
|
|
@ -51,7 +47,6 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
|||
}
|
||||
|
||||
Future<void> deleteFile() async {
|
||||
final messages = widget.galleryItems[currentIndex].messages;
|
||||
final confirmed = await showAlertDialog(
|
||||
context,
|
||||
context.lang.deleteImageTitle,
|
||||
|
|
@ -60,32 +55,26 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
|||
|
||||
if (!confirmed) return;
|
||||
|
||||
widget.galleryItems[currentIndex].imagePath?.deleteSync();
|
||||
widget.galleryItems[currentIndex].videoPath?.deleteSync();
|
||||
for (final message in messages) {
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
message.messageId,
|
||||
const MessagesCompanion(mediaStored: Value(false)),
|
||||
);
|
||||
}
|
||||
widget.galleryItems[currentIndex].mediaService.fullMediaRemoval();
|
||||
await twonlyDB.mediaFilesDao.deleteMediaFile(
|
||||
widget.galleryItems[currentIndex].mediaService.mediaFile.mediaId,
|
||||
);
|
||||
|
||||
widget.galleryItems.removeAt(currentIndex);
|
||||
setState(() {});
|
||||
await send.purgeSendMediaFiles();
|
||||
await received.purgeReceivedMediaFiles();
|
||||
if (mounted) {
|
||||
Navigator.pop(context, true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> exportFile() async {
|
||||
final item = widget.galleryItems[currentIndex];
|
||||
final item = widget.galleryItems[currentIndex].mediaService;
|
||||
|
||||
try {
|
||||
if (item.videoPath != null) {
|
||||
await saveVideoToGallery(item.videoPath!.path);
|
||||
} else if (item.imagePath != null) {
|
||||
final imageBytes = await item.imagePath!.readAsBytes();
|
||||
if (item.mediaFile.type == MediaType.video) {
|
||||
await saveVideoToGallery(item.storedPath.path);
|
||||
} else if (item.mediaFile.type == MediaType.image) {
|
||||
final imageBytes = await item.storedPath.readAsBytes();
|
||||
await saveImageToGallery(imageBytes);
|
||||
}
|
||||
if (!mounted) return;
|
||||
|
|
@ -132,14 +121,9 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ShareImageEditorView(
|
||||
videoFilePath:
|
||||
widget.galleryItems[currentIndex].videoPath,
|
||||
imageBytes: widget
|
||||
.galleryItems[currentIndex].imagePath
|
||||
?.readAsBytes(),
|
||||
mirrorVideo: false,
|
||||
mediaFileService: widget
|
||||
.galleryItems[currentIndex].mediaService,
|
||||
sharedFromGallery: true,
|
||||
useHighQuality: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -214,24 +198,25 @@ class _MemoriesPhotoSliderViewState extends State<MemoriesPhotoSliderView> {
|
|||
|
||||
PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
|
||||
final item = widget.galleryItems[index];
|
||||
return item.videoPath != null
|
||||
return item.mediaService.mediaFile.type == MediaType.video
|
||||
? PhotoViewGalleryPageOptions.customChild(
|
||||
child: VideoPlayerWrapper(
|
||||
videoPath: item.videoPath!,
|
||||
mirrorVideo: item.mirrorVideo,
|
||||
videoPath: item.mediaService.storedPath,
|
||||
),
|
||||
// childSize: const Size(300, 300),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 4.1,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: item.id),
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
tag: item.mediaService.mediaFile.mediaId),
|
||||
)
|
||||
: PhotoViewGalleryPageOptions(
|
||||
imageProvider: FileImage(item.imagePath!),
|
||||
imageProvider: FileImage(item.mediaService.storedPath),
|
||||
initialScale: PhotoViewComputedScale.contained,
|
||||
minScale: PhotoViewComputedScale.contained,
|
||||
maxScale: PhotoViewComputedScale.covered * 4.1,
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: item.id),
|
||||
heroAttributes: PhotoViewHeroAttributes(
|
||||
tag: item.mediaService.mediaFile.mediaId),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:twonly/src/database/daos/contacts.dao.dart';
|
|||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.component.dart';
|
||||
|
||||
class PrivacyViewBlockUsers extends StatefulWidget {
|
||||
const PrivacyViewBlockUsers({super.key});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/pow.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
|
|
@ -22,15 +20,6 @@ void main() {
|
|||
expect(await calculatePoW(Uint8List.fromList([41, 41, 41, 41]), 6), 33);
|
||||
});
|
||||
|
||||
test('test utils', () async {
|
||||
final list1 = Uint8List.fromList([41, 41, 41, 41, 41, 41, 41]);
|
||||
final list2 = Uint8List.fromList([42, 42, 42]);
|
||||
final combined = combineUint8Lists(list1, list2);
|
||||
final lists = extractUint8Lists(combined);
|
||||
expect(list1, lists[0]);
|
||||
expect(list2, lists[1]);
|
||||
});
|
||||
|
||||
test('encode hex', () async {
|
||||
final list1 = Uint8List.fromList([41, 41, 41, 41, 41, 41, 41]);
|
||||
expect(list1, hexToUint8List(uint8ListToHex(list1)));
|
||||
|
|
|
|||
Loading…
Reference in a new issue