Merge pull request #266 from twonlyapp/dev

Dev
This commit is contained in:
Tobi 2025-08-05 14:11:00 +02:00 committed by GitHub
commit 63e4a175d6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 5320 additions and 739 deletions

View file

@ -1,5 +1,18 @@
# Changelog # Changelog
## 0.0.60
- Improved logging to debug the 'Tap to load' issue.
==> If you encounter any issues, please send your debug log via the feedback button along with a short description of the error so that we can resolve them. :)
- Display your own avatar in the title bar of the chat list.
- Created a default avatar image in case none was set.
- Improved UI handling when requesting microphone access for the first time.
- Flutter SDK and dependencies upgraded.
- Multiple bug fixes.
## 0.0.59 ## 0.0.59
- Fixing media download error - Fixing media download error

View file

@ -0,0 +1,27 @@
<!-- Taken from: https://getavataaars.com/ -->
<svg width="264px" height="280px" viewBox="0 0 264 280" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path
d="M124,144.610951 L124,163 L128,163 L128,163 C167.764502,163 200,195.235498 200,235 L200,244 L0,244 L0,235 C-4.86974701e-15,195.235498 32.235498,163 72,163 L72,163 L76,163 L76,144.610951 C58.7626345,136.422372 46.3722246,119.687011 44.3051388,99.8812385 C38.4803105,99.0577866 34,94.0521096 34,88 L34,74 C34,68.0540074 38.3245733,63.1180731 44,62.1659169 L44,56 L44,56 C44,25.072054 69.072054,5.68137151e-15 100,0 L100,0 L100,0 C130.927946,-5.68137151e-15 156,25.072054 156,56 L156,62.1659169 C161.675427,63.1180731 166,68.0540074 166,74 L166,88 C166,94.0521096 161.51969,99.0577866 155.694861,99.8812385 C153.627775,119.687011 141.237365,136.422372 124,144.610951 Z"
id="react-path-3"></path>
</defs>
<g id="Avataaar" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-825.000000, -1100.000000)">
<g transform="translate(825.000000, 1100.000000)">
<g id="Avataaar" stroke-width="1" fill-rule="evenodd">
<g id="Body" transform="translate(32.000000, 36.000000)">
<mask id="react-mask-6" fill="white">
<use xlink:href="#react-path-3"></use>
</mask>
<g id="Skin/👶🏽-03-Brown" mask="url(#react-mask-6)" fill="#57CC99">
<g transform="translate(0.000000, 0.000000)" id="Color">
<rect x="0" y="0" width="264" height="280"></rect>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -19,10 +19,10 @@ PODS:
- Firebase/Messaging (11.15.0): - Firebase/Messaging (11.15.0):
- Firebase/CoreOnly - Firebase/CoreOnly
- FirebaseMessaging (~> 11.15.0) - FirebaseMessaging (~> 11.15.0)
- firebase_core (3.15.1): - firebase_core (3.15.2):
- Firebase/CoreOnly (= 11.15.0) - Firebase/CoreOnly (= 11.15.0)
- Flutter - Flutter
- firebase_messaging (15.2.9): - firebase_messaging (15.2.10):
- Firebase/Messaging (= 11.15.0) - Firebase/Messaging (= 11.15.0)
- firebase_core - firebase_core
- Flutter - Flutter
@ -204,28 +204,31 @@ PODS:
- sqflite_darwin (0.0.4): - sqflite_darwin (0.0.4):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (3.50.2): - sqlite3 (3.50.3):
- sqlite3/common (= 3.50.2) - sqlite3/common (= 3.50.3)
- sqlite3/common (3.50.2) - sqlite3/common (3.50.3)
- sqlite3/dbstatvtab (3.50.2): - sqlite3/dbstatvtab (3.50.3):
- sqlite3/common - sqlite3/common
- sqlite3/fts5 (3.50.2): - sqlite3/fts5 (3.50.3):
- sqlite3/common - sqlite3/common
- sqlite3/math (3.50.2): - sqlite3/math (3.50.3):
- sqlite3/common - sqlite3/common
- sqlite3/perf-threadsafe (3.50.2): - sqlite3/perf-threadsafe (3.50.3):
- sqlite3/common - sqlite3/common
- sqlite3/rtree (3.50.2): - sqlite3/rtree (3.50.3):
- sqlite3/common
- sqlite3/session (3.50.3):
- sqlite3/common - sqlite3/common
- sqlite3_flutter_libs (0.0.1): - sqlite3_flutter_libs (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sqlite3 (~> 3.50.1) - sqlite3 (~> 3.50.3)
- sqlite3/dbstatvtab - sqlite3/dbstatvtab
- sqlite3/fts5 - sqlite3/fts5
- sqlite3/math - sqlite3/math
- sqlite3/perf-threadsafe - sqlite3/perf-threadsafe
- sqlite3/rtree - sqlite3/rtree
- sqlite3/session
- SwiftProtobuf (1.30.0) - SwiftProtobuf (1.30.0)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
@ -364,8 +367,8 @@ SPEC CHECKSUMS:
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
firebase_core: ece862f94b2bc72ee0edbeec7ab5c7cb09fe1ab5 firebase_core: 995454a784ff288be5689b796deb9e9fa3601818
firebase_messaging: e1a5fae495603115be1d0183bc849da748734e2b firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707
FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae FirebaseAnalytics: 6433dfd311ba78084fc93bdfc145e8cb75740eae
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
@ -399,8 +402,8 @@ SPEC CHECKSUMS:
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc sqlite3: 83105acd294c9137c026e2da1931c30b4588ab81
sqlite3_flutter_libs: e7fc8c9ea2200ff3271f08f127842131746b70e2 sqlite3_flutter_libs: 616267f2fca40e9c6af8c5d82324e05667040b6e
SwiftProtobuf: 3697407f0d5b23bedeba9c2eaaf3ec6fdff69349 SwiftProtobuf: 3697407f0d5b23bedeba9c2eaaf3ec6fdff69349
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_compress: f2133a07762889d67f0711ac831faa26f956980e video_compress: f2133a07762889d67f0711ac831faa26f956980e

View file

@ -115,15 +115,6 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
} }
} }
Future<void> newMessageExchange(int userId) {
return updateContact(
userId,
ContactsCompanion(
lastMessageExchange: Value(DateTime.now()),
),
);
}
Stream<List<Contact>> watchNotAcceptedContacts() { Stream<List<Contact>> watchNotAcceptedContacts() {
return (select(contacts) return (select(contacts)
..where((t) => ..where((t) =>

View file

@ -1,43 +0,0 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:twonly/src/database/tables/media_download_table.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/log.dart';
part 'media_downloads_dao.g.dart';
@DriftAccessor(tables: [MediaDownloads])
class MediaDownloadsDao extends DatabaseAccessor<TwonlyDatabase>
with _$MediaDownloadsDaoMixin {
MediaDownloadsDao(super.db);
Future<void> updateMediaDownload(
int messageId, MediaDownloadsCompanion updatedValues) {
return (update(mediaDownloads)..where((c) => c.messageId.equals(messageId)))
.write(updatedValues);
}
Future<int?> insertMediaDownload(MediaDownloadsCompanion values) async {
try {
return await into(mediaDownloads).insert(values);
} catch (e) {
Log.error('Error while inserting media upload: $e');
return null;
}
}
Future<void> deleteMediaDownload(int messageId) {
return (delete(mediaDownloads)..where((t) => t.messageId.equals(messageId)))
.go();
}
SingleOrNullSelectable<MediaDownload> getMediaDownloadById(int messageId) {
return select(mediaDownloads)..where((t) => t.messageId.equals(messageId));
}
SingleOrNullSelectable<MediaDownload> getMediaDownloadByDownloadToken(
List<int> downloadToken) {
return select(mediaDownloads)
..where((t) => t.downloadToken.equals(json.encode(downloadToken)));
}
}

View file

@ -1,8 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'media_downloads_dao.dart';
// ignore_for_file: type=lint
mixin _$MediaDownloadsDaoMixin on DatabaseAccessor<TwonlyDatabase> {
$MediaDownloadsTable get mediaDownloads => attachedDatabase.mediaDownloads;
}

View file

@ -193,7 +193,9 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
Future<int?> insertMessage(MessagesCompanion message) async { Future<int?> insertMessage(MessagesCompanion message) async {
try { try {
await (update(contacts) await (update(contacts)
..where((c) => c.userId.equals(message.contactId.value))) ..where(
(c) => c.userId.equals(message.contactId.value),
))
.write(ContactsCompanion(lastMessageExchange: Value(DateTime.now()))); .write(ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
return await into(messages).insert(message); return await into(messages).insert(message);

View file

@ -1,8 +0,0 @@
import 'package:drift/drift.dart';
import 'package:twonly/src/database/tables/media_uploads_table.dart';
@DataClassName('MediaDownload')
class MediaDownloads extends Table {
IntColumn get messageId => integer()();
TextColumn get downloadToken => text().map(IntListTypeConverter())();
}

View file

@ -3,13 +3,11 @@ import 'package:drift_flutter/drift_flutter.dart'
show DriftNativeOptions, driftDatabase; show DriftNativeOptions, driftDatabase;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/daos/media_downloads_dao.dart';
import 'package:twonly/src/database/daos/media_uploads_dao.dart'; import 'package:twonly/src/database/daos/media_uploads_dao.dart';
import 'package:twonly/src/database/daos/message_retransmissions.dao.dart'; import 'package:twonly/src/database/daos/message_retransmissions.dao.dart';
import 'package:twonly/src/database/daos/messages_dao.dart'; import 'package:twonly/src/database/daos/messages_dao.dart';
import 'package:twonly/src/database/daos/signal_dao.dart'; import 'package:twonly/src/database/daos/signal_dao.dart';
import 'package:twonly/src/database/tables/contacts_table.dart'; import 'package:twonly/src/database/tables/contacts_table.dart';
import 'package:twonly/src/database/tables/media_download_table.dart';
import 'package:twonly/src/database/tables/media_uploads_table.dart'; import 'package:twonly/src/database/tables/media_uploads_table.dart';
import 'package:twonly/src/database/tables/message_retransmissions.dart'; import 'package:twonly/src/database/tables/message_retransmissions.dart';
import 'package:twonly/src/database/tables/messages_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
@ -29,7 +27,6 @@ part 'twonly_database.g.dart';
Contacts, Contacts,
Messages, Messages,
MediaUploads, MediaUploads,
MediaDownloads,
SignalIdentityKeyStores, SignalIdentityKeyStores,
SignalPreKeyStores, SignalPreKeyStores,
SignalSenderKeyStores, SignalSenderKeyStores,
@ -41,7 +38,6 @@ part 'twonly_database.g.dart';
MessagesDao, MessagesDao,
ContactsDao, ContactsDao,
MediaUploadsDao, MediaUploadsDao,
MediaDownloadsDao,
SignalDao, SignalDao,
MessageRetransmissionDao MessageRetransmissionDao
]) ])
@ -54,7 +50,7 @@ class TwonlyDatabase extends _$TwonlyDatabase {
TwonlyDatabase.forTesting(DatabaseConnection super.connection); TwonlyDatabase.forTesting(DatabaseConnection super.connection);
@override @override
int get schemaVersion => 15; int get schemaVersion => 16;
static QueryExecutor _openConnection() { static QueryExecutor _openConnection() {
return driftDatabase( return driftDatabase(
@ -91,7 +87,7 @@ class TwonlyDatabase extends _$TwonlyDatabase {
)); ));
}, },
from4To5: (m, schema) async { from4To5: (m, schema) async {
await m.createTable(mediaDownloads); await m.createTable(schema.mediaDownloads);
await m.addColumn(schema.messages, schema.messages.mediaDownloadId); await m.addColumn(schema.messages, schema.messages.mediaDownloadId);
await m.addColumn(schema.messages, schema.messages.mediaUploadId); await m.addColumn(schema.messages, schema.messages.mediaUploadId);
}, },
@ -140,6 +136,9 @@ class TwonlyDatabase extends _$TwonlyDatabase {
await m.addColumn( await m.addColumn(
schema.messages, schema.messages.mediaRetransmissionState); schema.messages, schema.messages.mediaRetransmissionState);
}, },
from15To16: (m, schema) async {
await m.deleteTable('media_downloads');
},
), ),
); );
} }
@ -164,7 +163,6 @@ class TwonlyDatabase extends _$TwonlyDatabase {
Future<void> deleteDataForTwonlySafe() async { Future<void> deleteDataForTwonlySafe() async {
await delete(messages).go(); await delete(messages).go();
await delete(messageRetransmissions).go(); await delete(messageRetransmissions).go();
await delete(mediaDownloads).go();
await delete(mediaUploads).go(); await delete(mediaUploads).go();
await update(contacts).write( await update(contacts).write(
const ContactsCompanion( const ContactsCompanion(

View file

@ -2341,206 +2341,6 @@ class MediaUploadsCompanion extends UpdateCompanion<MediaUpload> {
} }
} }
class $MediaDownloadsTable extends MediaDownloads
with TableInfo<$MediaDownloadsTable, MediaDownload> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$MediaDownloadsTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _messageIdMeta =
const VerificationMeta('messageId');
@override
late final GeneratedColumn<int> messageId = GeneratedColumn<int>(
'message_id', aliasedName, false,
type: DriftSqlType.int, requiredDuringInsert: true);
@override
late final GeneratedColumnWithTypeConverter<List<int>, String> downloadToken =
GeneratedColumn<String>('download_token', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true)
.withConverter<List<int>>(
$MediaDownloadsTable.$converterdownloadToken);
@override
List<GeneratedColumn> get $columns => [messageId, downloadToken];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'media_downloads';
@override
VerificationContext validateIntegrity(Insertable<MediaDownload> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('message_id')) {
context.handle(_messageIdMeta,
messageId.isAcceptableOrUnknown(data['message_id']!, _messageIdMeta));
} else if (isInserting) {
context.missing(_messageIdMeta);
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => const {};
@override
MediaDownload map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return MediaDownload(
messageId: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}message_id'])!,
downloadToken: $MediaDownloadsTable.$converterdownloadToken.fromSql(
attachedDatabase.typeMapping.read(
DriftSqlType.string, data['${effectivePrefix}download_token'])!),
);
}
@override
$MediaDownloadsTable createAlias(String alias) {
return $MediaDownloadsTable(attachedDatabase, alias);
}
static TypeConverter<List<int>, String> $converterdownloadToken =
IntListTypeConverter();
}
class MediaDownload extends DataClass implements Insertable<MediaDownload> {
final int messageId;
final List<int> downloadToken;
const MediaDownload({required this.messageId, required this.downloadToken});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['message_id'] = Variable<int>(messageId);
{
map['download_token'] = Variable<String>(
$MediaDownloadsTable.$converterdownloadToken.toSql(downloadToken));
}
return map;
}
MediaDownloadsCompanion toCompanion(bool nullToAbsent) {
return MediaDownloadsCompanion(
messageId: Value(messageId),
downloadToken: Value(downloadToken),
);
}
factory MediaDownload.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return MediaDownload(
messageId: serializer.fromJson<int>(json['messageId']),
downloadToken: serializer.fromJson<List<int>>(json['downloadToken']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'messageId': serializer.toJson<int>(messageId),
'downloadToken': serializer.toJson<List<int>>(downloadToken),
};
}
MediaDownload copyWith({int? messageId, List<int>? downloadToken}) =>
MediaDownload(
messageId: messageId ?? this.messageId,
downloadToken: downloadToken ?? this.downloadToken,
);
MediaDownload copyWithCompanion(MediaDownloadsCompanion data) {
return MediaDownload(
messageId: data.messageId.present ? data.messageId.value : this.messageId,
downloadToken: data.downloadToken.present
? data.downloadToken.value
: this.downloadToken,
);
}
@override
String toString() {
return (StringBuffer('MediaDownload(')
..write('messageId: $messageId, ')
..write('downloadToken: $downloadToken')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(messageId, downloadToken);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is MediaDownload &&
other.messageId == this.messageId &&
other.downloadToken == this.downloadToken);
}
class MediaDownloadsCompanion extends UpdateCompanion<MediaDownload> {
final Value<int> messageId;
final Value<List<int>> downloadToken;
final Value<int> rowid;
const MediaDownloadsCompanion({
this.messageId = const Value.absent(),
this.downloadToken = const Value.absent(),
this.rowid = const Value.absent(),
});
MediaDownloadsCompanion.insert({
required int messageId,
required List<int> downloadToken,
this.rowid = const Value.absent(),
}) : messageId = Value(messageId),
downloadToken = Value(downloadToken);
static Insertable<MediaDownload> custom({
Expression<int>? messageId,
Expression<String>? downloadToken,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
if (messageId != null) 'message_id': messageId,
if (downloadToken != null) 'download_token': downloadToken,
if (rowid != null) 'rowid': rowid,
});
}
MediaDownloadsCompanion copyWith(
{Value<int>? messageId,
Value<List<int>>? downloadToken,
Value<int>? rowid}) {
return MediaDownloadsCompanion(
messageId: messageId ?? this.messageId,
downloadToken: downloadToken ?? this.downloadToken,
rowid: rowid ?? this.rowid,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (messageId.present) {
map['message_id'] = Variable<int>(messageId.value);
}
if (downloadToken.present) {
map['download_token'] = Variable<String>($MediaDownloadsTable
.$converterdownloadToken
.toSql(downloadToken.value));
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('MediaDownloadsCompanion(')
..write('messageId: $messageId, ')
..write('downloadToken: $downloadToken, ')
..write('rowid: $rowid')
..write(')'))
.toString();
}
}
class $SignalIdentityKeyStoresTable extends SignalIdentityKeyStores class $SignalIdentityKeyStoresTable extends SignalIdentityKeyStores
with TableInfo<$SignalIdentityKeyStoresTable, SignalIdentityKeyStore> { with TableInfo<$SignalIdentityKeyStoresTable, SignalIdentityKeyStore> {
@override @override
@ -4568,7 +4368,6 @@ abstract class _$TwonlyDatabase extends GeneratedDatabase {
late final $ContactsTable contacts = $ContactsTable(this); late final $ContactsTable contacts = $ContactsTable(this);
late final $MessagesTable messages = $MessagesTable(this); late final $MessagesTable messages = $MessagesTable(this);
late final $MediaUploadsTable mediaUploads = $MediaUploadsTable(this); late final $MediaUploadsTable mediaUploads = $MediaUploadsTable(this);
late final $MediaDownloadsTable mediaDownloads = $MediaDownloadsTable(this);
late final $SignalIdentityKeyStoresTable signalIdentityKeyStores = late final $SignalIdentityKeyStoresTable signalIdentityKeyStores =
$SignalIdentityKeyStoresTable(this); $SignalIdentityKeyStoresTable(this);
late final $SignalPreKeyStoresTable signalPreKeyStores = late final $SignalPreKeyStoresTable signalPreKeyStores =
@ -4587,8 +4386,6 @@ abstract class _$TwonlyDatabase extends GeneratedDatabase {
late final ContactsDao contactsDao = ContactsDao(this as TwonlyDatabase); late final ContactsDao contactsDao = ContactsDao(this as TwonlyDatabase);
late final MediaUploadsDao mediaUploadsDao = late final MediaUploadsDao mediaUploadsDao =
MediaUploadsDao(this as TwonlyDatabase); MediaUploadsDao(this as TwonlyDatabase);
late final MediaDownloadsDao mediaDownloadsDao =
MediaDownloadsDao(this as TwonlyDatabase);
late final SignalDao signalDao = SignalDao(this as TwonlyDatabase); late final SignalDao signalDao = SignalDao(this as TwonlyDatabase);
late final MessageRetransmissionDao messageRetransmissionDao = late final MessageRetransmissionDao messageRetransmissionDao =
MessageRetransmissionDao(this as TwonlyDatabase); MessageRetransmissionDao(this as TwonlyDatabase);
@ -4600,7 +4397,6 @@ abstract class _$TwonlyDatabase extends GeneratedDatabase {
contacts, contacts,
messages, messages,
mediaUploads, mediaUploads,
mediaDownloads,
signalIdentityKeyStores, signalIdentityKeyStores,
signalPreKeyStores, signalPreKeyStores,
signalSenderKeyStores, signalSenderKeyStores,
@ -5999,139 +5795,6 @@ typedef $$MediaUploadsTableProcessedTableManager = ProcessedTableManager<
), ),
MediaUpload, MediaUpload,
PrefetchHooks Function()>; PrefetchHooks Function()>;
typedef $$MediaDownloadsTableCreateCompanionBuilder = MediaDownloadsCompanion
Function({
required int messageId,
required List<int> downloadToken,
Value<int> rowid,
});
typedef $$MediaDownloadsTableUpdateCompanionBuilder = MediaDownloadsCompanion
Function({
Value<int> messageId,
Value<List<int>> downloadToken,
Value<int> rowid,
});
class $$MediaDownloadsTableFilterComposer
extends Composer<_$TwonlyDatabase, $MediaDownloadsTable> {
$$MediaDownloadsTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<int> get messageId => $composableBuilder(
column: $table.messageId, builder: (column) => ColumnFilters(column));
ColumnWithTypeConverterFilters<List<int>, List<int>, String>
get downloadToken => $composableBuilder(
column: $table.downloadToken,
builder: (column) => ColumnWithTypeConverterFilters(column));
}
class $$MediaDownloadsTableOrderingComposer
extends Composer<_$TwonlyDatabase, $MediaDownloadsTable> {
$$MediaDownloadsTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<int> get messageId => $composableBuilder(
column: $table.messageId, builder: (column) => ColumnOrderings(column));
ColumnOrderings<String> get downloadToken => $composableBuilder(
column: $table.downloadToken,
builder: (column) => ColumnOrderings(column));
}
class $$MediaDownloadsTableAnnotationComposer
extends Composer<_$TwonlyDatabase, $MediaDownloadsTable> {
$$MediaDownloadsTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<int> get messageId =>
$composableBuilder(column: $table.messageId, builder: (column) => column);
GeneratedColumnWithTypeConverter<List<int>, String> get downloadToken =>
$composableBuilder(
column: $table.downloadToken, builder: (column) => column);
}
class $$MediaDownloadsTableTableManager extends RootTableManager<
_$TwonlyDatabase,
$MediaDownloadsTable,
MediaDownload,
$$MediaDownloadsTableFilterComposer,
$$MediaDownloadsTableOrderingComposer,
$$MediaDownloadsTableAnnotationComposer,
$$MediaDownloadsTableCreateCompanionBuilder,
$$MediaDownloadsTableUpdateCompanionBuilder,
(
MediaDownload,
BaseReferences<_$TwonlyDatabase, $MediaDownloadsTable, MediaDownload>
),
MediaDownload,
PrefetchHooks Function()> {
$$MediaDownloadsTableTableManager(
_$TwonlyDatabase db, $MediaDownloadsTable table)
: super(TableManagerState(
db: db,
table: table,
createFilteringComposer: () =>
$$MediaDownloadsTableFilterComposer($db: db, $table: table),
createOrderingComposer: () =>
$$MediaDownloadsTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$$MediaDownloadsTableAnnotationComposer($db: db, $table: table),
updateCompanionCallback: ({
Value<int> messageId = const Value.absent(),
Value<List<int>> downloadToken = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
MediaDownloadsCompanion(
messageId: messageId,
downloadToken: downloadToken,
rowid: rowid,
),
createCompanionCallback: ({
required int messageId,
required List<int> downloadToken,
Value<int> rowid = const Value.absent(),
}) =>
MediaDownloadsCompanion.insert(
messageId: messageId,
downloadToken: downloadToken,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.toList(),
prefetchHooksCallback: null,
));
}
typedef $$MediaDownloadsTableProcessedTableManager = ProcessedTableManager<
_$TwonlyDatabase,
$MediaDownloadsTable,
MediaDownload,
$$MediaDownloadsTableFilterComposer,
$$MediaDownloadsTableOrderingComposer,
$$MediaDownloadsTableAnnotationComposer,
$$MediaDownloadsTableCreateCompanionBuilder,
$$MediaDownloadsTableUpdateCompanionBuilder,
(
MediaDownload,
BaseReferences<_$TwonlyDatabase, $MediaDownloadsTable, MediaDownload>
),
MediaDownload,
PrefetchHooks Function()>;
typedef $$SignalIdentityKeyStoresTableCreateCompanionBuilder typedef $$SignalIdentityKeyStoresTableCreateCompanionBuilder
= SignalIdentityKeyStoresCompanion Function({ = SignalIdentityKeyStoresCompanion Function({
required int deviceId, required int deviceId,
@ -7481,8 +7144,6 @@ class $TwonlyDatabaseManager {
$$MessagesTableTableManager(_db, _db.messages); $$MessagesTableTableManager(_db, _db.messages);
$$MediaUploadsTableTableManager get mediaUploads => $$MediaUploadsTableTableManager get mediaUploads =>
$$MediaUploadsTableTableManager(_db, _db.mediaUploads); $$MediaUploadsTableTableManager(_db, _db.mediaUploads);
$$MediaDownloadsTableTableManager get mediaDownloads =>
$$MediaDownloadsTableTableManager(_db, _db.mediaDownloads);
$$SignalIdentityKeyStoresTableTableManager get signalIdentityKeyStores => $$SignalIdentityKeyStoresTableTableManager get signalIdentityKeyStores =>
$$SignalIdentityKeyStoresTableTableManager( $$SignalIdentityKeyStoresTableTableManager(
_db, _db.signalIdentityKeyStores); _db, _db.signalIdentityKeyStores);

View file

@ -3497,6 +3497,222 @@ class Shape20 extends i0.VersionedTable {
columnsByName['encryption_data']! as i1.GeneratedColumn<String>; columnsByName['encryption_data']! as i1.GeneratedColumn<String>;
} }
final class Schema16 extends i0.VersionedSchema {
Schema16({required super.database}) : super(version: 16);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
contacts,
messages,
mediaUploads,
signalIdentityKeyStores,
signalPreKeyStores,
signalSenderKeyStores,
signalSessionStores,
signalContactPreKeys,
signalContactSignedPreKeys,
messageRetransmissions,
];
late final Shape13 contacts = Shape13(
source: i0.VersionedTable(
entityName: 'contacts',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(user_id)',
],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
_column_4,
_column_5,
_column_6,
_column_7,
_column_8,
_column_9,
_column_39,
_column_53,
_column_57,
_column_54,
_column_40,
_column_10,
_column_11,
_column_12,
_column_13,
_column_14,
_column_55,
_column_15,
_column_16,
],
attachedDatabase: database,
),
alias: null);
late final Shape19 messages = Shape19(
source: i0.VersionedTable(
entityName: 'messages',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_17,
_column_18,
_column_19,
_column_48,
_column_49,
_column_20,
_column_21,
_column_22,
_column_52,
_column_23,
_column_24,
_column_25,
_column_70,
_column_26,
_column_27,
_column_28,
_column_29,
_column_30,
],
attachedDatabase: database,
),
alias: null);
late final Shape20 mediaUploads = Shape20(
source: i0.VersionedTable(
entityName: 'media_uploads',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_41,
_column_42,
_column_56,
_column_44,
_column_45,
],
attachedDatabase: database,
),
alias: null);
late final Shape2 signalIdentityKeyStores = Shape2(
source: i0.VersionedTable(
entityName: 'signal_identity_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_31,
_column_32,
_column_33,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 signalPreKeyStores = Shape3(
source: i0.VersionedTable(
entityName: 'signal_pre_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(pre_key_id)',
],
columns: [
_column_34,
_column_35,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape4 signalSenderKeyStores = Shape4(
source: i0.VersionedTable(
entityName: 'signal_sender_key_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(sender_key_name)',
],
columns: [
_column_36,
_column_37,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 signalSessionStores = Shape5(
source: i0.VersionedTable(
entityName: 'signal_session_stores',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(device_id, name)',
],
columns: [
_column_31,
_column_32,
_column_38,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape14 signalContactPreKeys = Shape14(
source: i0.VersionedTable(
entityName: 'signal_contact_pre_keys',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(contact_id, pre_key_id)',
],
columns: [
_column_58,
_column_34,
_column_35,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape15 signalContactSignedPreKeys = Shape15(
source: i0.VersionedTable(
entityName: 'signal_contact_signed_pre_keys',
withoutRowId: false,
isStrict: false,
tableConstraints: [
'PRIMARY KEY(contact_id)',
],
columns: [
_column_58,
_column_59,
_column_60,
_column_61,
_column_10,
],
attachedDatabase: database,
),
alias: null);
late final Shape18 messageRetransmissions = Shape18(
source: i0.VersionedTable(
entityName: 'message_retransmissions',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_62,
_column_63,
_column_64,
_column_65,
_column_66,
_column_69,
_column_67,
],
attachedDatabase: database,
),
alias: null);
}
i0.MigrationStepWithVersion migrationSteps({ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2, required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3, required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
@ -3512,6 +3728,7 @@ i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13, required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14, required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15, required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
}) { }) {
return (currentVersion, database) async { return (currentVersion, database) async {
switch (currentVersion) { switch (currentVersion) {
@ -3585,6 +3802,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema); final migrator = i1.Migrator(database, schema);
await from14To15(migrator, schema); await from14To15(migrator, schema);
return 15; return 15;
case 15:
final schema = Schema16(database: database);
final migrator = i1.Migrator(database, schema);
await from15To16(migrator, schema);
return 16;
default: default:
throw ArgumentError.value('Unknown migration from $currentVersion'); throw ArgumentError.value('Unknown migration from $currentVersion');
} }
@ -3606,6 +3828,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13, required Future<void> Function(i1.Migrator m, Schema13 schema) from12To13,
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14, required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15, required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
}) => }) =>
i0.VersionedSchema.stepByStepHelper( i0.VersionedSchema.stepByStepHelper(
step: migrationSteps( step: migrationSteps(
@ -3623,4 +3846,5 @@ i1.OnUpgrade stepByStep({
from12To13: from12To13, from12To13: from12To13,
from13To14: from13To14, from13To14: from13To14,
from14To15: from14To15, from14To15: from14To15,
from15To16: from15To16,
)); ));

View file

@ -15,7 +15,7 @@ Color getMessageColorFromType(MessageContent content, BuildContext context) {
color = context.color.primary; color = context.color.primary;
} else { } else {
if (content.isVideo) { if (content.isVideo) {
color = const Color.fromARGB(255, 240, 243, 33); color = const Color.fromARGB(255, 243, 33, 208);
} else { } else {
color = Colors.redAccent; color = Colors.redAccent;
} }

View file

@ -49,6 +49,9 @@ class UserData {
@JsonKey(defaultValue: true) @JsonKey(defaultValue: true)
bool useHighQuality = true; bool useHighQuality = true;
@JsonKey(defaultValue: false)
bool requestedAudioPermission = false;
@JsonKey(defaultValue: true) @JsonKey(defaultValue: true)
bool showFeedbackShortcut = true; bool showFeedbackShortcut = true;

View file

@ -26,6 +26,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
ThemeMode.system ThemeMode.system
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt() ..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
..useHighQuality = json['useHighQuality'] as bool? ?? true ..useHighQuality = json['useHighQuality'] as bool? ?? true
..requestedAudioPermission =
json['requestedAudioPermission'] as bool? ?? false
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true ..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?) ..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)
@ -84,6 +86,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'defaultShowTime': instance.defaultShowTime, 'defaultShowTime': instance.defaultShowTime,
'useHighQuality': instance.useHighQuality, 'useHighQuality': instance.useHighQuality,
'requestedAudioPermission': instance.requestedAudioPermission,
'showFeedbackShortcut': instance.showFeedbackShortcut, 'showFeedbackShortcut': instance.showFeedbackShortcut,
'preSelectedEmojies': instance.preSelectedEmojies, 'preSelectedEmojies': instance.preSelectedEmojies,
'autoDownloadOptions': instance.autoDownloadOptions, 'autoDownloadOptions': instance.autoDownloadOptions,

View file

@ -120,32 +120,34 @@ Future<void> handleDownloadStatusUpdateInternal(
Mutex protectDownload = Mutex(); Mutex protectDownload = Mutex();
Future<void> startDownloadMedia(Message message, bool force) async { Future<void> startDownloadMedia(Message message, bool force) async {
if (message.contentJson == null) return; Log.info(
'Download blocked for ${message.messageId} because of network state.');
final content = MessageContent.fromJson( if (message.contentJson == null) {
message.kind, jsonDecode(message.contentJson!) as Map); Log.error('Content of ${message.messageId} not found.');
await handleMediaError(message);
if (content is! MediaMessageContent) return; return;
if (content.downloadToken == null) return;
var media = await twonlyDB.mediaDownloadsDao
.getMediaDownloadById(message.messageId)
.getSingleOrNull();
if (media == null) {
await twonlyDB.mediaDownloadsDao.insertMediaDownload(
MediaDownloadsCompanion(
messageId: Value(message.messageId),
downloadToken: Value(content.downloadToken!),
),
);
media = await twonlyDB.mediaDownloadsDao
.getMediaDownloadById(message.messageId)
.getSingleOrNull();
} }
if (media == null) return; final content = MessageContent.fromJson(
message.kind,
jsonDecode(message.contentJson!) as Map,
);
if (content is! MediaMessageContent) {
Log.error('Content of ${message.messageId} is not media file.');
await handleMediaError(message);
return;
}
if (content.downloadToken == null) {
Log.error('Download token not defined for ${message.messageId}.');
await handleMediaError(message);
return;
}
if (!force && !await isAllowedToDownload(content.isVideo)) { if (!force && !await isAllowedToDownload(content.isVideo)) {
Log.warn(
'Download blocked for ${message.messageId} because of network state.');
return; return;
} }
@ -185,10 +187,10 @@ Future<void> startDownloadMedia(Message message, bool force) async {
try { try {
final task = DownloadTask( final task = DownloadTask(
url: apiUrl, url: apiUrl,
taskId: 'download_${media.messageId}', taskId: 'download_${message.messageId}',
directory: 'media/received/', directory: 'media/received/',
baseDirectory: BaseDirectory.applicationSupport, baseDirectory: BaseDirectory.applicationSupport,
filename: '${media.messageId}.encrypted', filename: '${message.messageId}.encrypted',
priority: 0, priority: 0,
retries: 10, retries: 10,
); );
@ -198,7 +200,7 @@ Future<void> startDownloadMedia(Message message, bool force) async {
); );
try { try {
await downloadFileFast(media.messageId, apiUrl); await downloadFileFast(message.messageId, apiUrl);
} catch (e) { } catch (e) {
Log.error('Fast download failed: $e'); Log.error('Fast download failed: $e');
await FileDownloader().enqueue(task); await FileDownloader().enqueue(task);

View file

@ -555,8 +555,9 @@ Future<void> handleMediaUpload(MediaUpload media) async {
timestamp: media.metadata!.messageSendAt, timestamp: media.metadata!.messageSendAt,
); );
final plaintextContent = final plaintextContent = Uint8List.fromList(
Uint8List.fromList(gzip.encode(utf8.encode(jsonEncode(msg.toJson())))); gzip.encode(utf8.encode(jsonEncode(msg.toJson()))),
);
final contact = await twonlyDB.contactsDao final contact = await twonlyDB.contactsDao
.getContactByUserId(message.contactId) .getContactByUserId(message.contactId)

View file

@ -398,9 +398,12 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
); );
if (messageId == null) { if (messageId == null) {
Log.error('could not insert message into db');
return client.Response()..error = ErrorCode.InternalError; return client.Response()..error = ErrorCode.InternalError;
} }
Log.info('Inserted a new message with id: $messageId');
if (message.kind == MessageKind.media) { if (message.kind == MessageKind.media) {
await twonlyDB.contactsDao.incFlameCounter( await twonlyDB.contactsDao.incFlameCounter(
fromUserId, fromUserId,

View file

@ -13,6 +13,7 @@ import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart';
import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart'; import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart';
import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart'; import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart';
@ -171,6 +172,19 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Future<void> initAsync() async { Future<void> initAsync() async {
hasAudioPermission = await Permission.microphone.isGranted; hasAudioPermission = await Permission.microphone.isGranted;
if (!hasAudioPermission) {
final user = await getUser();
if (user != null) {
if (!user.requestedAudioPermission) {
await updateUserdata((u) {
u.requestedAudioPermission = true;
return u;
});
await requestMicrophonePermission();
}
}
}
if (!mounted) return; if (!mounted) return;
setState(() {}); setState(() {});
} }

View file

@ -10,7 +10,6 @@ import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/headline.dart'; import 'package:twonly/src/views/components/headline.dart';
import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/verified_shield.dart';
class BestFriendsSelector extends StatelessWidget { class BestFriendsSelector extends StatelessWidget {
const BestFriendsSelector({ const BestFriendsSelector({
@ -157,13 +156,6 @@ class UserCheckbox extends StatelessWidget {
children: [ children: [
Row( Row(
children: [ children: [
if (isRealTwonly)
Padding(
padding: const EdgeInsets.only(right: 2),
child: VerifiedShield(
user,
size: 12,
)),
Text( Text(
displayName.length > 8 displayName.length > 8
? '${displayName.substring(0, 8)}...' ? '${displayName.substring(0, 8)}...'

View file

@ -21,7 +21,6 @@ import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart'; import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart';
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart'; 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/alert_dialog.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:twonly/src/views/settings/subscription/subscription.view.dart';
@ -242,16 +241,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Colors.white, : Colors.white,
onPressed: () async { onPressed: () async {
if (widget.sendTo != null) {
if (!widget.sendTo!.verified) {
await showAlertDialog(
context,
context.lang.shareImageUserNotVerified,
context.lang.shareImageUserNotVerifiedDesc,
);
return;
}
}
_isRealTwonly = !_isRealTwonly; _isRealTwonly = !_isRealTwonly;
if (_isRealTwonly) { if (_isRealTwonly) {
maxShowTime = 12; maxShowTime = 12;

View file

@ -54,7 +54,6 @@ class _ShareImageView extends State<ShareImageView> {
bool sendingImage = false; bool sendingImage = false;
bool hideArchivedUsers = true; bool hideArchivedUsers = true;
final TextEditingController searchUserName = TextEditingController(); final TextEditingController searchUserName = TextEditingController();
bool showRealTwonlyWarning = false;
late StreamSubscription<List<Contact>> contactSub; late StreamSubscription<List<Contact>> contactSub;
String lastQuery = ''; String lastQuery = '';
@ -150,15 +149,6 @@ class _ShareImageView extends State<ShareImageView> {
} }
void updateStatus(int userId, bool checked) { void updateStatus(int userId, bool checked) {
if (widget.isRealTwonly) {
final user = contacts.firstWhere((x) => x.userId == userId);
if (!user.verified) {
showRealTwonlyWarning = true;
setState(() {});
return;
}
}
showRealTwonlyWarning = false;
widget.updateStatus(userId, checked); widget.updateStatus(userId, checked);
setState(() {}); setState(() {});
} }
@ -175,13 +165,6 @@ class _ShareImageView extends State<ShareImageView> {
const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10),
child: Column( child: Column(
children: [ children: [
if (showRealTwonlyWarning)
Text(
context.lang.shareImageAllTwonlyWarning,
style: const TextStyle(color: Colors.orange, fontSize: 13),
textAlign: TextAlign.center,
),
if (showRealTwonlyWarning) const SizedBox(height: 10),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField( child: TextField(

View file

@ -10,6 +10,7 @@ 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/messages_table.dart'; import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/providers/connection.provider.dart';
import 'package:twonly/src/services/api/media_download.dart'; import 'package:twonly/src/services/api/media_download.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
@ -21,14 +22,15 @@ import 'package:twonly/src/views/chats/chat_list_components/connection_info.comp
import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart'; import 'package:twonly/src/views/chats/chat_list_components/feedback_btn.dart';
import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart'; import 'package:twonly/src/views/chats/chat_list_components/last_message_time.dart';
import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart';
import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart';
import 'package:twonly/src/views/chats/media_viewer.view.dart'; import 'package:twonly/src/views/chats/media_viewer.view.dart';
import 'package:twonly/src/views/chats/start_new_chat.view.dart'; 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/flame.dart';
import 'package:twonly/src/views/components/initialsavatar.dart'; import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/message_send_state_icon.dart';
import 'package:twonly/src/views/components/notification_badge.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.dart';
import 'package:twonly/src/views/settings/help/changelog.view.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'; import 'package:twonly/src/views/settings/settings_main.view.dart';
import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
import 'package:twonly/src/views/tutorial/tutorials.dart'; import 'package:twonly/src/views/tutorial/tutorials.dart';
@ -43,6 +45,7 @@ class _ChatListViewState extends State<ChatListView> {
late StreamSubscription<List<Contact>> _contactsSub; late StreamSubscription<List<Contact>> _contactsSub;
List<Contact> _contacts = []; List<Contact> _contacts = [];
List<Contact> _pinnedContacts = []; List<Contact> _pinnedContacts = [];
UserData? _user;
GlobalKey firstUserListItemKey = GlobalKey(); GlobalKey firstUserListItemKey = GlobalKey();
GlobalKey searchForOtherUsers = GlobalKey(); GlobalKey searchForOtherUsers = GlobalKey();
@ -76,6 +79,9 @@ class _ChatListViewState extends State<ChatListView> {
final user = await getUser(); final user = await getUser();
if (user == null) return; if (user == null) return;
setState(() {
_user = user;
});
final changeLog = await rootBundle.loadString('CHANGELOG.md'); final changeLog = await rootBundle.loadString('CHANGELOG.md');
final changeLogHash = final changeLogHash =
(await compute(Sha256().hash, changeLog.codeUnits)).bytes; (await compute(Sha256().hash, changeLog.codeUnits)).bytes;
@ -112,6 +118,23 @@ class _ChatListViewState extends State<ChatListView> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Row(children: [ title: Row(children: [
GestureDetector(
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return const ProfileView();
}));
_user = await getUser();
if (!mounted) return;
setState(() {});
},
child: ContactAvatar(
userData: _user,
fontSize: 14,
color: context.color.onSurface.withAlpha(20),
),
),
const SizedBox(width: 10),
const Text('twonly '), const Text('twonly '),
if (planId != 'Free') if (planId != 'Free')
GestureDetector( GestureDetector(
@ -164,13 +187,16 @@ class _ChatListViewState extends State<ChatListView> {
}, },
), ),
IconButton( IconButton(
onPressed: () { onPressed: () async {
Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const SettingsMainView(), builder: (context) => const SettingsMainView(),
), ),
); );
_user = await getUser();
if (!mounted) return;
setState(() {});
}, },
icon: const FaIcon(FontAwesomeIcons.gear, size: 19), icon: const FaIcon(FontAwesomeIcons.gear, size: 19),
) )
@ -298,6 +324,7 @@ class _UserListItem extends State<UserListItem> {
late StreamSubscription<List<Message>> lastMessageStream; late StreamSubscription<List<Message>> lastMessageStream;
List<Message> previewMessages = []; List<Message> previewMessages = [];
bool hasNonOpenedMediaFile = false;
@override @override
void initState() { void initState() {
@ -352,6 +379,17 @@ class _UserListItem extends State<UserListItem> {
} }
} }
final msgs =
previewMessages.where((x) => x.kind == MessageKind.media).toList();
if (msgs.isNotEmpty &&
msgs.first.kind == MessageKind.media &&
msgs.first.messageOtherId != null &&
msgs.first.openedAt == null) {
hasNonOpenedMediaFile = true;
} else {
hasNonOpenedMediaFile = false;
}
lastMessages = newLastMessages; lastMessages = newLastMessages;
messagesNotOpened = newMessagesNotOpened; messagesNotOpened = newMessagesNotOpened;
setState(() { setState(() {
@ -368,12 +406,10 @@ class _UserListItem extends State<UserListItem> {
)); ));
return; return;
} }
final msgs =
previewMessages.where((x) => x.kind == MessageKind.media).toList(); if (hasNonOpenedMediaFile) {
if (msgs.isNotEmpty && final msgs =
msgs.first.kind == MessageKind.media && previewMessages.where((x) => x.kind == MessageKind.media).toList();
msgs.first.messageOtherId != null &&
msgs.first.openedAt == null) {
switch (msgs.first.downloadState) { switch (msgs.first.downloadState) {
case DownloadState.pending: case DownloadState.pending:
await startDownloadMedia(msgs.first, true); await startDownloadMedia(msgs.first, true);
@ -447,12 +483,18 @@ class _UserListItem extends State<UserListItem> {
onPressed: () { onPressed: () {
Navigator.push(context, MaterialPageRoute( Navigator.push(context, MaterialPageRoute(
builder: (context) { builder: (context) {
return CameraSendToView(widget.user); if (hasNonOpenedMediaFile) {
return ChatMessagesView(widget.user);
} else {
return CameraSendToView(widget.user);
}
}, },
)); ));
}, },
icon: FaIcon( icon: FaIcon(
FontAwesomeIcons.camera, hasNonOpenedMediaFile
? FontAwesomeIcons.solidComments
: FontAwesomeIcons.camera,
color: context.color.outline.withAlpha(150), color: context.color.outline.withAlpha(150),
), ),
), ),

View file

@ -297,7 +297,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
children: [ children: [
Text(getContactDisplayName(user)), Text(getContactDisplayName(user)),
const SizedBox(width: 10), const SizedBox(width: 10),
VerifiedShield(key: verifyShieldKey, user), if (user.verified)
VerifiedShield(key: verifyShieldKey, user)
], ],
), ),
), ),

View file

@ -12,20 +12,23 @@ class ChatDateChip extends StatelessWidget {
final formattedDate = item.isTime final formattedDate = item.isTime
? DateFormat.Hm(Localizations.localeOf(context).toLanguageTag()) ? DateFormat.Hm(Localizations.localeOf(context).toLanguageTag())
.format(item.time!) .format(item.time!)
: '${DateFormat.Hm(Localizations.localeOf(context).toLanguageTag()).format(item.date!)} ${DateFormat.yMd(Localizations.localeOf(context).toLanguageTag()).format(item.date!)}'; : '${DateFormat.Hm(Localizations.localeOf(context).toLanguageTag()).format(item.date!)}\n${DateFormat.yMd(Localizations.localeOf(context).toLanguageTag()).format(item.date!)}';
return Center( return Center(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black.withAlpha(40), color: isDarkMode(context)
? const Color.fromARGB(255, 38, 38, 38)
: Colors.black.withAlpha(40),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: Text( child: Text(
formattedDate, formattedDate,
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: isDarkMode(context) ? Colors.white : Colors.black, color: isDarkMode(context) ? Colors.white : Colors.grey,
), ),
), ),
), ),

View file

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/views/components/message_send_state_icon.dart'; import 'package:twonly/src/views/chats/chat_messages_components/message_send_state_icon.dart';
import 'package:twonly/src/views/memories/memories_item_thumbnail.dart'; import 'package:twonly/src/views/memories/memories_item_thumbnail.dart';
import 'package:twonly/src/views/memories/memories_photo_slider.view.dart'; import 'package:twonly/src/views/memories/memories_photo_slider.view.dart';

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -11,21 +10,20 @@ class ContactAvatar extends StatelessWidget {
this.contact, this.contact,
this.userData, this.userData,
this.fontSize = 20, this.fontSize = 20,
this.color,
}); });
final Contact? contact; final Contact? contact;
final UserData? userData; final UserData? userData;
final double? fontSize; final double? fontSize;
final Color? color;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var displayName = '';
String? avatarSvg; String? avatarSvg;
if (contact != null) { if (contact != null) {
displayName = getContactDisplayName(contact!).replaceAll('\u0336', '');
avatarSvg = contact!.avatarSvg; avatarSvg = contact!.avatarSvg;
} else if (userData != null) { } else if (userData != null) {
displayName = userData!.displayName;
avatarSvg = userData!.avatarSvg; avatarSvg = userData!.avatarSvg;
} else { } else {
return Container(); return Container();
@ -33,148 +31,34 @@ class ContactAvatar extends StatelessWidget {
final proSize = (fontSize == null) ? 40 : (fontSize! * 2); final proSize = (fontSize == null) ? 40 : (fontSize! * 2);
if (avatarSvg != null) { return Container(
return Container( constraints: BoxConstraints(
constraints: BoxConstraints( minHeight: 2 * (fontSize ?? 20),
minHeight: 2 * (fontSize ?? 20), minWidth: 2 * (fontSize ?? 20),
minWidth: 2 * (fontSize ?? 20), maxWidth: 2 * (fontSize ?? 20),
maxWidth: 2 * (fontSize ?? 20), maxHeight: 2 * (fontSize ?? 20),
maxHeight: 2 * (fontSize ?? 20), ),
), child: Center(
child: Center( child: ClipRRect(
child: ClipRRect( borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12), child: Container(
child: SizedBox( height: proSize as double,
height: proSize as double, width: proSize,
width: proSize, color: color,
child: Center( child: Center(
child: SvgPicture.string( child: avatarSvg == null
avatarSvg, ? SvgPicture.asset('assets/images/default_avatar.svg')
errorBuilder: (context, error, stackTrace) { : SvgPicture.string(
Log.error('$error'); avatarSvg,
return Container(); errorBuilder: (context, error, stackTrace) {
}, Log.error('$error');
), return Container();
), },
),
), ),
), ),
), ),
);
}
// Extract initials from the displayName
final nameParts = displayName.split(' ');
var initials = nameParts.map((part) => part[0]).join().toUpperCase();
if (initials.length > 2) {
initials = initials[0] + initials[1];
} else if (initials.length == 1) {
initials = displayName[0] + displayName[1];
}
initials = initials.toUpperCase();
// Generate a color based on the initials (you can customize this logic)
final avatarColor = _getColorFromUsername(
displayName, Theme.of(context).brightness == Brightness.dark);
final Widget child = Text(
initials,
style: TextStyle(
color: _getTextColor(avatarColor),
fontWeight: FontWeight.normal,
fontSize: fontSize,
), ),
); );
final isPro = initials[0] == 'T';
if (isPro) {
return Container(
constraints: BoxConstraints(
minHeight: 2 * (fontSize ?? 20),
minWidth: 2 * (fontSize ?? 20),
maxWidth: 2 * (fontSize ?? 20),
maxHeight: 2 * (fontSize ?? 20),
),
child: Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Container(
height: proSize as double,
width: proSize,
//padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10),
color: avatarColor,
child: Center(child: child),
),
),
),
);
} else {
return CircleAvatar(
backgroundColor: avatarColor,
radius: fontSize,
child: child,
);
}
}
Color _getTextColor(Color color) {
const value = 100.0;
// Ensure the value does not exceed the RGB limits
final newRed = ((color.r * 255) - value).clamp(0, 255).round();
final newGreen = (color.g * 255 - value).clamp(0, 255).round();
final newBlue = (color.b * 255 - value).clamp(0, 255).round();
return Color.fromARGB((color.a * 255).round(), newRed, newGreen, newBlue);
}
Color _getColorFromUsername(String displayName, bool isDarkMode) {
// Define color lists for light and dark themes
final lightColors = <Color>[
Colors.red,
Colors.green,
Colors.blue,
Colors.orange,
Colors.purple,
Colors.teal,
Colors.amber,
Colors.indigo,
Colors.cyan,
Colors.lime,
Colors.pink,
Colors.brown,
Colors.grey,
];
final darkColors = <Color>[
const Color.fromARGB(255, 246, 227, 254), // Light Lavender
const Color.fromARGB(255, 246, 216, 215), // Light Pink
const Color.fromARGB(255, 226, 236, 235), // Light Teal
const Color.fromARGB(255, 255, 224, 178), // Light Yellow
const Color.fromARGB(255, 255, 182, 193), // Light Pink (Hot Pink)
const Color.fromARGB(255, 173, 216, 230), // Light Blue
const Color.fromARGB(255, 221, 160, 221), // Plum
const Color.fromARGB(255, 255, 228, 196), // Bisque
const Color.fromARGB(255, 240, 230, 140), // Khaki
const Color.fromARGB(255, 255, 192, 203), // Pink
const Color.fromARGB(255, 255, 218, 185), // Peach Puff
const Color.fromARGB(255, 255, 160, 122), // Light Salmon
const Color.fromARGB(255, 135, 206, 250), // Light Sky Blue
const Color.fromARGB(255, 255, 228, 225), // Misty Rose
const Color.fromARGB(255, 240, 248, 255), // Alice Blue
const Color.fromARGB(255, 255, 250, 205), // Lemon Chiffon
const Color.fromARGB(255, 255, 218, 185), // Peach Puff
];
// Simple logic to generate a hash from initials
final hash =
displayName.codeUnits.fold(0, (prev, element) => prev + element);
// Select the appropriate color list based on the current theme brightness
final colors = isDarkMode ? darkColors : lightColors;
// Use the hash to select a color from the list
return colors[hash % colors.length];
} }
} }

View file

@ -8,7 +8,6 @@ import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/chats/chat_messages.view.dart'; import 'package:twonly/src/views/chats/chat_messages.view.dart';
import 'package:twonly/src/views/contact/contact.view.dart'; import 'package:twonly/src/views/contact/contact.view.dart';
import 'package:twonly/src/views/contact/contact_verify.view.dart';
class UserContextMenu extends StatefulWidget { class UserContextMenu extends StatefulWidget {
const UserContextMenu({ const UserContextMenu({
@ -58,18 +57,6 @@ class _UserContextMenuState extends State<UserContextMenu> {
}, },
child: const FaIcon(FontAwesomeIcons.boxOpen), child: const FaIcon(FontAwesomeIcons.boxOpen),
), ),
if (!widget.contact.verified)
PieAction(
tooltip: Text(context.lang.contextMenuVerifyUser),
onSelect: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) {
return ContactVerifyView(widget.contact);
},
));
},
child: const Icon(Icons.gpp_maybe_rounded),
),
PieAction( PieAction(
tooltip: Text(context.lang.contextMenuOpenChat), tooltip: Text(context.lang.contextMenuOpenChat),
onSelect: () { onSelect: () {

View file

@ -310,7 +310,7 @@ class _SubscriptionViewState extends State<SubscriptionView> {
})); }));
}, },
), ),
if (isPayingUser) if (isPayingUser || currentPlan == 'Tester')
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.userPlus, icon: FontAwesomeIcons.userPlus,
text: context.lang.manageAdditionalUsers, text: context.lang.manageAdditionalUsers,
@ -387,8 +387,10 @@ class PlanCard extends StatelessWidget {
]; ];
case 'Family': case 'Family':
features = [ features = [
context.lang.familyFeature1, context.lang.proFeature1,
context.lang.familyFeature2, context.lang.familyFeature2,
context.lang.proFeature3,
context.lang.proFeature4,
]; ];
default: default:
} }

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: a5788040810bd84400bc209913fbc40f388cded7cdf95ee2f5d2bff7e38d5241 sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.58" version: "1.3.59"
adaptive_number: adaptive_number:
dependency: transitive dependency: transitive
description: description:
@ -29,10 +29,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: de617bfdc64f3d8b00835ec2957441ceca0a29cdf7881f7ab231bc14f71159c0 sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.5.6" version: "7.7.1"
archive: archive:
dependency: transitive dependency: transitive
description: description:
@ -69,10 +69,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: background_downloader name: background_downloader
sha256: d3016a9eb584f6cb16384c8b4a008943c39119730d60046044349b5dbbda4ccb sha256: c59bff0b66a6704bed8bfb09c67571df88167906e0f5543a722373b3d180a743
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.2.2" version: "9.2.3"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -85,10 +85,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4" version: "3.0.0"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -109,26 +109,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4" version: "3.0.0"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4" version: "2.6.0"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.1.2" version: "9.2.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -141,10 +141,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.10.1" version: "8.11.0"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@ -181,18 +181,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: camera_android_camerax name: camera_android_camerax
sha256: "4b6c1bef4270c39df96402c4d62f2348c3bb2bbaefd0883b9dbd58f426306ad0" sha256: "58b8fe843a3c83fd1273c00cb35f5a8ae507f6cc9b2029bcf7e2abba499e28d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.19" version: "0.6.19+1"
camera_avfoundation: camera_avfoundation:
dependency: transitive dependency: transitive
description: description:
name: camera_avfoundation name: camera_avfoundation
sha256: "14d7698b26e95a7db371bee1b07358245e5100640ab5e07c036be23f51383d43" sha256: "04e1f052ef268085a4f0550389211cc46005a9af015e444c7b1ee7aa19332e5d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.20+2" version: "0.9.20+6"
camera_platform_interface: camera_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -333,10 +333,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "3.1.1"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@ -373,26 +373,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: drift name: drift
sha256: e60c715f045dd33624fc533efb0075e057debec9f39e83843e518f488a0e21fb sha256: "6aaea757f53bb035e8a3baedf3d1d53a79d6549a6c13d84f7546509da9372c7c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.27.0" version: "2.28.1"
drift_dev: drift_dev:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: drift_dev name: drift_dev
sha256: "7ad88b8982e753eadcdbc0ea7c7d30500598af733601428b5c9d264baf5106d6" sha256: "2fc05ad458a7c562755bf0cae11178dfc58387a416829b78d4da5155a61465fd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.27.0" version: "2.28.1"
drift_flutter: drift_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: drift_flutter name: drift_flutter
sha256: "0cadbf3b8733409a6cf61d18ba2e94e149df81df7de26f48ae0695b48fd71922" sha256: ccfb42bc942e59f81500b16228df59cf8eb40d2fbd96637ff677df923350af7b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.4" version: "0.2.5"
ed25519_edwards: ed25519_edwards:
dependency: transitive dependency: transitive
description: description:
@ -461,10 +461,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: c6e8a6bf883d8ddd0dec39be90872daca65beaa6f4cff0051ed3b16c56b82e9f sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.15.1" version: "3.15.2"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -485,26 +485,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_messaging name: firebase_messaging
sha256: "0f3363f97672eb9f65609fa00ed2f62cc8ec93e7e2d4def99726f9165d3d8a73" sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.2.9" version: "15.2.10"
firebase_messaging_platform_interface: firebase_messaging_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_platform_interface name: firebase_messaging_platform_interface
sha256: "7a05ef119a14c5f6a9440d1e0223bcba20c8daf555450e119c4c477bf2c3baa9" sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.6.9" version: "4.6.10"
firebase_messaging_web: firebase_messaging_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_web name: firebase_messaging_web
sha256: a4547f76da2a905190f899eb4d0150e1d0fd52206fce469d9f05ae15bb68b2c5 sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.10.9" version: "3.10.10"
fixnum: fixnum:
dependency: "direct main" dependency: "direct main"
description: description:
@ -642,10 +642,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: edae0c34573233ab03f5ba1f07465e55c384743893042cb19e010b4ee8541c12 sha256: "20ca0a9c82ce0c855ac62a2e580ab867f3fbea82680a90647f7953832d0850ae"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "19.3.0" version: "19.4.0"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
@ -666,10 +666,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_windows name: flutter_local_notifications_windows
sha256: "593625e6833c0def4853b361c5276464b314983c6c819178bf0fa5aba2540d86" sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.2"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -759,10 +759,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: font_awesome_flutter name: font_awesome_flutter
sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a sha256: f50ce90dbe26d977415b9540400d6778bef00894aced6358ae578abd92b14b10
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.8.0" version: "10.9.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -775,10 +775,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: gal name: gal
sha256: "2771519c8b29f784d5e27f4efc2667667eef51c6c47cccaa0435a8fe8aa208e4" sha256: "969598f986789127fd407a750413249e1352116d4c2be66e81837ffeeaafdfee"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
get: get:
dependency: "direct main" dependency: "direct main"
description: description:
@ -815,10 +815,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: hashlib name: hashlib
sha256: c742f4250067e52686e2bbc73013794e748511473baa7f875289681436daa4ed sha256: "145889c76c9530481e90b4b97c02ad817b637f25dadcb5795988f5aa0800f173"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.1.0"
hashlib_codecs: hashlib_codecs:
dependency: transitive dependency: transitive
description: description:
@ -879,10 +879,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image_picker_android name: image_picker_android
sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.12+23" version: "0.8.12+24"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@ -975,10 +975,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.9.5" version: "6.10.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -1031,10 +1031,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: local_auth_android name: local_auth_android
sha256: "63ad7ca6396290626dc0cb34725a939e4cfe965d80d36112f08d49cf13a8136e" sha256: "82b2bdeee2199a510d3b7716121e96a6609da86693bb0863edd8566355406b79"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.49" version: "1.0.50"
local_auth_darwin: local_auth_darwin:
dependency: transitive dependency: transitive
description: description:
@ -1358,10 +1358,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: protobuf name: protobuf
sha256: "579fe5557eae58e3adca2e999e38f02441d8aa908703854a9e0a0f47fa857731" sha256: "6153efcc92a06910918f3db8231fd2cf828ac81e50ebd87adc8f8a8cb3caff0e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.1.1"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1523,10 +1523,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" sha256: fc787b1f89ceac9580c3616f899c9a447413cbdac1df071302127764c023a134
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "3.0.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
@ -1571,10 +1571,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.5" version: "2.5.6"
sqflite_darwin: sqflite_darwin:
dependency: transitive dependency: transitive
description: description:
@ -1595,26 +1595,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqlite3 name: sqlite3
sha256: c0503c69b44d5714e6abbf4c1f51a3c3cc42b75ce785f44404765e4635481d38 sha256: dd806fff004a0aeb01e208b858dbc649bc72104670d425a81a6dd17698535f6e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.6" version: "2.8.0"
sqlite3_flutter_libs: sqlite3_flutter_libs:
dependency: transitive dependency: transitive
description: description:
name: sqlite3_flutter_libs name: sqlite3_flutter_libs
sha256: e07232b998755fe795655c56d1f5426e0190c9c435e1752d39e7b1cd33699c71 sha256: fd996da5515a73aacd0a04ae7063db5fe8df42670d974df4c3ee538c652eef2e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.34" version: "0.5.38"
sqlparser: sqlparser:
dependency: transitive dependency: transitive
description: description:
name: sqlparser name: sqlparser
sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee" sha256: "7c859c803cf7e9a84d6db918bac824545045692bbe94a6386bd3a45132235d09"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.41.0" version: "0.41.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -1691,10 +1691,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: tutorial_coach_mark name: tutorial_coach_mark
sha256: "9cdb721165d1cfb6e9b1910a1af1b3570fa6caa5059cf1506fcbd00bf7102abf" sha256: ccc4a2026d361d8a71011d0f131a2278add1a154ef43e33dfd165babbb551c17
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -1835,18 +1835,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: video_player_android name: video_player_android
sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73" sha256: "0fabf59eea728a6a887f29f2818eafbefb4b37c727dbb62dccef56c9287a692f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.8.7" version: "2.8.10"
video_player_avfoundation: video_player_avfoundation:
dependency: transitive dependency: transitive
description: description:
name: video_player_avfoundation name: video_player_avfoundation
sha256: "9fedd55023249f3a02738c195c906b4e530956191febf0838e37d0dac912f953" sha256: "509ef9cfe7a3379783ccf306d45f5b5fc9db747401f956ce31c963417019e48e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.8.0" version: "2.8.2"
video_player_platform_interface: video_player_platform_interface:
dependency: transitive dependency: transitive
description: description:

View file

@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
publish_to: 'none' publish_to: 'none'
version: 0.0.59+59 version: 0.0.60+60
environment: environment:
sdk: ^3.6.0 sdk: ^3.6.0

View file

@ -18,6 +18,7 @@ import 'schema_v12.dart' as v12;
import 'schema_v13.dart' as v13; import 'schema_v13.dart' as v13;
import 'schema_v14.dart' as v14; import 'schema_v14.dart' as v14;
import 'schema_v15.dart' as v15; import 'schema_v15.dart' as v15;
import 'schema_v16.dart' as v16;
class GeneratedHelper implements SchemaInstantiationHelper { class GeneratedHelper implements SchemaInstantiationHelper {
@override @override
@ -53,6 +54,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v14.DatabaseAtV14(db); return v14.DatabaseAtV14(db);
case 15: case 15:
return v15.DatabaseAtV15(db); return v15.DatabaseAtV15(db);
case 16:
return v16.DatabaseAtV16(db);
default: default:
throw MissingSchemaException(version, versions); throw MissingSchemaException(version, versions);
} }
@ -73,6 +76,7 @@ class GeneratedHelper implements SchemaInstantiationHelper {
12, 12,
13, 13,
14, 14,
15 15,
16
]; ];
} }

File diff suppressed because it is too large Load diff