mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
add storage by chats view
This commit is contained in:
parent
5786effc5b
commit
5914b81845
9 changed files with 441 additions and 0 deletions
|
|
@ -10,6 +10,16 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'mediafiles.dao.g.dart';
|
part 'mediafiles.dao.g.dart';
|
||||||
|
|
||||||
|
class ChatMediaFiles {
|
||||||
|
const ChatMediaFiles({
|
||||||
|
required this.group,
|
||||||
|
required this.mediaFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Group group;
|
||||||
|
final List<MediaFile> mediaFiles;
|
||||||
|
}
|
||||||
|
|
||||||
@DriftAccessor(tables: [MediaFiles, Messages])
|
@DriftAccessor(tables: [MediaFiles, Messages])
|
||||||
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
with _$MediaFilesDaoMixin {
|
with _$MediaFilesDaoMixin {
|
||||||
|
|
@ -165,6 +175,45 @@ class MediaFilesDao extends DatabaseAccessor<TwonlyDB>
|
||||||
return query.map((row) => row.readTable(mediaFiles)).watch();
|
return query.map((row) => row.readTable(mediaFiles)).watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Watches all chats and their distinct referenced media records.
|
||||||
|
Stream<List<ChatMediaFiles>> watchMediaFilesByChat() {
|
||||||
|
final query = select(db.groups).join([
|
||||||
|
leftOuterJoin(
|
||||||
|
db.messages,
|
||||||
|
db.messages.groupId.equalsExp(db.groups.groupId),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
leftOuterJoin(
|
||||||
|
mediaFiles,
|
||||||
|
mediaFiles.mediaId.equalsExp(db.messages.mediaId),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return query.watch().map((rows) {
|
||||||
|
final groupsById = <String, Group>{};
|
||||||
|
final mediaByGroupId = <String, Map<String, MediaFile>>{};
|
||||||
|
|
||||||
|
for (final row in rows) {
|
||||||
|
final group = row.readTable(db.groups);
|
||||||
|
groupsById[group.groupId] = group;
|
||||||
|
final mediaFile = row.readTableOrNull(mediaFiles);
|
||||||
|
if (mediaFile != null) {
|
||||||
|
(mediaByGroupId[group.groupId] ??= {})[mediaFile.mediaId] = mediaFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupsById.entries
|
||||||
|
.map(
|
||||||
|
(entry) => ChatMediaFiles(
|
||||||
|
group: entry.value,
|
||||||
|
mediaFiles:
|
||||||
|
mediaByGroupId[entry.key]?.values.toList() ?? const [],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Stream<List<MediaFile>> watchChatListMediaFiles() {
|
Stream<List<MediaFile>> watchChatListMediaFiles() {
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'''
|
'''
|
||||||
|
|
|
||||||
|
|
@ -4496,6 +4496,36 @@ abstract class AppLocalizations {
|
||||||
/// **'Local only'**
|
/// **'Local only'**
|
||||||
String get settingsStorageLocalOnly;
|
String get settingsStorageLocalOnly;
|
||||||
|
|
||||||
|
/// No description provided for @settingsStorageByChat.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Storage by chat'**
|
||||||
|
String get settingsStorageByChat;
|
||||||
|
|
||||||
|
/// No description provided for @settingsStorageTemporaryMediaDescription.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Temporary media files are sent media files that have not been stored. They remain on your device until the receiver has received and opened them.'**
|
||||||
|
String get settingsStorageTemporaryMediaDescription;
|
||||||
|
|
||||||
|
/// No description provided for @settingsStorageNoChats.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No chats found.'**
|
||||||
|
String get settingsStorageNoChats;
|
||||||
|
|
||||||
|
/// No description provided for @settingsStorageStoredMediaCount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count, plural, =1 {1 stored file} other {{count} stored files}}'**
|
||||||
|
String settingsStorageStoredMediaCount(num count);
|
||||||
|
|
||||||
|
/// No description provided for @settingsStorageTemporaryMediaCount.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count, plural, =1 {1 temporary file} other {{count} temporary files}}'**
|
||||||
|
String settingsStorageTemporaryMediaCount(num count);
|
||||||
|
|
||||||
/// No description provided for @settingsStorageDisableBackupTitle.
|
/// No description provided for @settingsStorageDisableBackupTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -2604,6 +2604,38 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get settingsStorageLocalOnly => 'Nur lokal';
|
String get settingsStorageLocalOnly => 'Nur lokal';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageByChat => 'Speicherplatz pro Chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageTemporaryMediaDescription =>
|
||||||
|
'Temporäre Mediendateien sind gesendete Medien, die nicht gespeichert wurden. Sie bleiben auf deinem Gerät, bis der Empfänger sie erhalten und geöffnet hat.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageNoChats => 'Keine Chats gefunden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String settingsStorageStoredMediaCount(num count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count gespeicherte Dateien',
|
||||||
|
one: '1 gespeicherte Datei',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String settingsStorageTemporaryMediaCount(num count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count temporäre Dateien',
|
||||||
|
one: '1 temporäre Datei',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsStorageDisableBackupTitle => 'Backup deaktivieren?';
|
String get settingsStorageDisableBackupTitle => 'Backup deaktivieren?';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2581,6 +2581,38 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get settingsStorageLocalOnly => 'Local only';
|
String get settingsStorageLocalOnly => 'Local only';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageByChat => 'Storage by chat';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageTemporaryMediaDescription =>
|
||||||
|
'Temporary media files are sent media files that have not been stored. They remain on your device until the receiver has received and opened them.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settingsStorageNoChats => 'No chats found.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String settingsStorageStoredMediaCount(num count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count stored files',
|
||||||
|
one: '1 stored file',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String settingsStorageTemporaryMediaCount(num count) {
|
||||||
|
String _temp0 = intl.Intl.pluralLogic(
|
||||||
|
count,
|
||||||
|
locale: localeName,
|
||||||
|
other: '$count temporary files',
|
||||||
|
one: '1 temporary file',
|
||||||
|
);
|
||||||
|
return '$_temp0';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get settingsStorageDisableBackupTitle => 'Disable Backup?';
|
String get settingsStorageDisableBackupTitle => 'Disable Backup?';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -989,6 +989,25 @@
|
||||||
"settingsStorageCloudOnly": "Nur Cloud",
|
"settingsStorageCloudOnly": "Nur Cloud",
|
||||||
"settingsStorageLocalAndCloud": "Lokal & Cloud",
|
"settingsStorageLocalAndCloud": "Lokal & Cloud",
|
||||||
"settingsStorageLocalOnly": "Nur lokal",
|
"settingsStorageLocalOnly": "Nur lokal",
|
||||||
|
"settingsStorageByChat": "Speicherplatz pro Chat",
|
||||||
|
"settingsStorageTemporaryMediaDescription": "Temporäre Mediendateien sind gesendete Medien, die nicht gespeichert wurden. Sie bleiben auf deinem Gerät, bis der Empfänger sie erhalten und geöffnet hat.",
|
||||||
|
"settingsStorageNoChats": "Keine Chats gefunden.",
|
||||||
|
"settingsStorageStoredMediaCount": "{count, plural, =1 {1 gespeicherte Datei} other {{count} gespeicherte Dateien}}",
|
||||||
|
"@settingsStorageStoredMediaCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "num"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settingsStorageTemporaryMediaCount": "{count, plural, =1 {1 temporäre Datei} other {{count} temporäre Dateien}}",
|
||||||
|
"@settingsStorageTemporaryMediaCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "num"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"settingsStorageDisableBackupTitle": "Backup deaktivieren?",
|
"settingsStorageDisableBackupTitle": "Backup deaktivieren?",
|
||||||
"settingsStorageDisableBackupBody": "Dadurch werden alle gesicherten Erinnerungen aus der Cloud gelöscht. Es gibt {count, plural, =1 {*1 Bild*, das} other {*{count} Bilder*, die}} nur in der Cloud und nicht lokal gespeichert sind. Diese gehen *dauerhaft verloren*. Fortfahren?",
|
"settingsStorageDisableBackupBody": "Dadurch werden alle gesicherten Erinnerungen aus der Cloud gelöscht. Es gibt {count, plural, =1 {*1 Bild*, das} other {*{count} Bilder*, die}} nur in der Cloud und nicht lokal gespeichert sind. Diese gehen *dauerhaft verloren*. Fortfahren?",
|
||||||
"@settingsStorageDisableBackupBody": {
|
"@settingsStorageDisableBackupBody": {
|
||||||
|
|
|
||||||
|
|
@ -999,6 +999,25 @@
|
||||||
"settingsStorageCloudOnly": "Cloud only",
|
"settingsStorageCloudOnly": "Cloud only",
|
||||||
"settingsStorageLocalAndCloud": "Local and cloud",
|
"settingsStorageLocalAndCloud": "Local and cloud",
|
||||||
"settingsStorageLocalOnly": "Local only",
|
"settingsStorageLocalOnly": "Local only",
|
||||||
|
"settingsStorageByChat": "Storage by chat",
|
||||||
|
"settingsStorageTemporaryMediaDescription": "Temporary media files are sent media files that have not been stored. They remain on your device until the receiver has received and opened them.",
|
||||||
|
"settingsStorageNoChats": "No chats found.",
|
||||||
|
"settingsStorageStoredMediaCount": "{count, plural, =1 {1 stored file} other {{count} stored files}}",
|
||||||
|
"@settingsStorageStoredMediaCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "num"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settingsStorageTemporaryMediaCount": "{count, plural, =1 {1 temporary file} other {{count} temporary files}}",
|
||||||
|
"@settingsStorageTemporaryMediaCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "num"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"settingsStorageDisableBackupTitle": "Disable Backup?",
|
"settingsStorageDisableBackupTitle": "Disable Backup?",
|
||||||
"settingsStorageDisableBackupBody": "This will delete all backed up memories from the cloud. There {count, plural, =1 {is *1 image*} other {are *{count} images*}} only stored in the cloud that will be *permanently lost*. Are you sure you want to proceed?",
|
"settingsStorageDisableBackupBody": "This will delete all backed up memories from the cloud. There {count, plural, =1 {is *1 image*} other {are *{count} images*}} only stored in the cloud that will be *permanently lost*. Are you sure you want to proceed?",
|
||||||
"@settingsStorageDisableBackupBody": {
|
"@settingsStorageDisableBackupBody": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/database/daos/mediafiles.dao.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
|
|
||||||
|
class ChatStorageOverviewView extends StatefulWidget {
|
||||||
|
const ChatStorageOverviewView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatStorageOverviewView> createState() =>
|
||||||
|
_ChatStorageOverviewViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatStorageOverviewViewState extends State<ChatStorageOverviewView> {
|
||||||
|
late final Stream<List<ChatMediaFiles>> _chatMediaFilesStream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_chatMediaFilesStream = twonlyDB.mediaFilesDao.watchMediaFilesByChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(context.lang.settingsStorageByChat),
|
||||||
|
),
|
||||||
|
body: StreamBuilder<List<ChatMediaFiles>>(
|
||||||
|
stream: _chatMediaFilesStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Center(child: Text('Error: ${snapshot.error}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
final summaries =
|
||||||
|
(snapshot.data ?? [])
|
||||||
|
.map(_ChatStorageSummary.fromChatMediaFiles)
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) {
|
||||||
|
final sizeComparison = b.totalBytes.compareTo(a.totalBytes);
|
||||||
|
if (sizeComparison != 0) return sizeComparison;
|
||||||
|
return a.group.groupName.toLowerCase().compareTo(
|
||||||
|
b.group.groupName.toLowerCase(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
_TemporaryMediaExplanation(
|
||||||
|
text: context.lang.settingsStorageTemporaryMediaDescription,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (summaries.isEmpty)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||||
|
child: Text(
|
||||||
|
context.lang.settingsStorageNoChats,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodyLarge?.copyWith(color: Colors.grey),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
...summaries.map(
|
||||||
|
(summary) => ListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
leading: AvatarIcon(group: summary.group),
|
||||||
|
title: Text(summary.group.groupName),
|
||||||
|
subtitle: Text(
|
||||||
|
'${context.lang.settingsStorageStoredMediaCount(summary.storedMediaCount)}'
|
||||||
|
' • '
|
||||||
|
'${context.lang.settingsStorageTemporaryMediaCount(summary.temporaryMediaCount)}',
|
||||||
|
),
|
||||||
|
trailing: Text(
|
||||||
|
formatBytes(summary.totalBytes),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TemporaryMediaExplanation extends StatelessWidget {
|
||||||
|
const _TemporaryMediaExplanation({required this.text});
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info_outline_rounded,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(child: Text(text)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatStorageSummary {
|
||||||
|
const _ChatStorageSummary({
|
||||||
|
required this.group,
|
||||||
|
required this.storedBytes,
|
||||||
|
required this.temporaryBytes,
|
||||||
|
required this.storedMediaCount,
|
||||||
|
required this.temporaryMediaCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _ChatStorageSummary.fromChatMediaFiles(ChatMediaFiles chat) {
|
||||||
|
var storedBytes = 0;
|
||||||
|
var temporaryBytes = 0;
|
||||||
|
var storedMediaCount = 0;
|
||||||
|
var temporaryMediaCount = 0;
|
||||||
|
|
||||||
|
for (final mediaFile in chat.mediaFiles) {
|
||||||
|
final mediaFileService = MediaFileService(mediaFile);
|
||||||
|
final storedSize = _fileSize(mediaFileService.storedPath);
|
||||||
|
final temporarySize = _fileSize(mediaFileService.tempPath);
|
||||||
|
|
||||||
|
if (storedSize > 0) {
|
||||||
|
storedMediaCount++;
|
||||||
|
storedBytes += storedSize;
|
||||||
|
}
|
||||||
|
if (temporarySize > 0) {
|
||||||
|
temporaryMediaCount++;
|
||||||
|
temporaryBytes += temporarySize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ChatStorageSummary(
|
||||||
|
group: chat.group,
|
||||||
|
storedBytes: storedBytes,
|
||||||
|
temporaryBytes: temporaryBytes,
|
||||||
|
storedMediaCount: storedMediaCount,
|
||||||
|
temporaryMediaCount: temporaryMediaCount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Group group;
|
||||||
|
final int storedBytes;
|
||||||
|
final int temporaryBytes;
|
||||||
|
final int storedMediaCount;
|
||||||
|
final int temporaryMediaCount;
|
||||||
|
|
||||||
|
int get totalBytes => storedBytes + temporaryBytes;
|
||||||
|
|
||||||
|
static int _fileSize(File file) {
|
||||||
|
try {
|
||||||
|
if (!file.existsSync()) return 0;
|
||||||
|
return file.lengthSync();
|
||||||
|
} on FileSystemException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:twonly/src/providers/purchases.provider.dart';
|
||||||
import 'package:twonly/src/services/subscription.service.dart';
|
import 'package:twonly/src/services/subscription.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||||
|
import 'package:twonly/src/visual/views/settings/data_and_storage/chat_storage_overview.view.dart';
|
||||||
import 'package:twonly/src/visual/views/settings/data_and_storage/storage_contents.view.dart';
|
import 'package:twonly/src/visual/views/settings/data_and_storage/storage_contents.view.dart';
|
||||||
|
|
||||||
class ManageStorageView extends StatefulWidget {
|
class ManageStorageView extends StatefulWidget {
|
||||||
|
|
@ -179,6 +180,16 @@ class _ManageStorageViewState extends State<ManageStorageView> {
|
||||||
child: Text(context.lang.settingsStorageContents),
|
child: Text(context.lang.settingsStorageContents),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Align(
|
||||||
|
child: MyButton(
|
||||||
|
variant: MyButtonVariant.secondaryMiddle,
|
||||||
|
onPressed: () => context.navPush(
|
||||||
|
const ChatStorageOverviewView(),
|
||||||
|
),
|
||||||
|
child: Text(context.lang.settingsStorageByChat),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
62
test/drift/mediafiles_dao_test.dart
Normal file
62
test/drift/mediafiles_dao_test.dart
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:drift/native.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late TwonlyDB database;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
database = TwonlyDB(NativeDatabase.memory());
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => database.close());
|
||||||
|
|
||||||
|
test(
|
||||||
|
'chat media includes empty groups and deduplicates message references',
|
||||||
|
() async {
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO groups(group_id, group_name) VALUES('chat-1', 'Friends')",
|
||||||
|
);
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO groups(group_id, group_name) VALUES('chat-2', 'Family')",
|
||||||
|
);
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO groups(group_id, group_name) VALUES('chat-3', 'Empty')",
|
||||||
|
);
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO media_files(media_id, type, stored, size_in_bytes) "
|
||||||
|
"VALUES('not-stored', 'image', 0, 100)",
|
||||||
|
);
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO media_files(media_id, type, stored, size_in_bytes) "
|
||||||
|
"VALUES('stored', 'video', 1, 200)",
|
||||||
|
);
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO messages(group_id, message_id, type, media_id) "
|
||||||
|
"VALUES('chat-1', 'message-1', 'media', 'not-stored')",
|
||||||
|
);
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO messages(group_id, message_id, type, media_id) "
|
||||||
|
"VALUES('chat-1', 'message-2', 'media', 'not-stored')",
|
||||||
|
);
|
||||||
|
await database.customStatement(
|
||||||
|
"INSERT INTO messages(group_id, message_id, type, media_id) "
|
||||||
|
"VALUES('chat-2', 'message-3', 'media', 'stored')",
|
||||||
|
);
|
||||||
|
|
||||||
|
final chats = await database.mediaFilesDao.watchMediaFilesByChat().first;
|
||||||
|
|
||||||
|
expect(chats, hasLength(3));
|
||||||
|
final friends = chats.singleWhere(
|
||||||
|
(chat) => chat.group.groupId == 'chat-1',
|
||||||
|
);
|
||||||
|
expect(friends.mediaFiles, hasLength(1));
|
||||||
|
expect(friends.mediaFiles.single.mediaId, 'not-stored');
|
||||||
|
expect(friends.mediaFiles.single.stored, isFalse);
|
||||||
|
final empty = chats.singleWhere(
|
||||||
|
(chat) => chat.group.groupId == 'chat-3',
|
||||||
|
);
|
||||||
|
expect(empty.mediaFiles, isEmpty);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue