fixing all linter errors

This commit is contained in:
otsmr 2025-07-15 13:38:38 +02:00
parent 575c0ca8ae
commit c70413cf6a
81 changed files with 653 additions and 666 deletions

View file

@ -14,6 +14,7 @@ analyzer:
- "lib/src/model/protobuf/**" - "lib/src/model/protobuf/**"
- "lib/src/model/protobuf/api/websocket/**" - "lib/src/model/protobuf/api/websocket/**"
- "lib/generated/**" - "lib/generated/**"
- "dependencies/**"
- "test/drift/**" - "test/drift/**"
- "**.g.dart" - "**.g.dart"

View file

@ -106,7 +106,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
Locale('en', ''), Locale('en', ''),
Locale('de', ''), Locale('de', ''),
], ],
onGenerateTitle: (BuildContext context) => "twonly", onGenerateTitle: (BuildContext context) => 'twonly',
theme: ThemeData( theme: ThemeData(
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF57CC99), seedColor: const Color(0xFF57CC99),
@ -132,8 +132,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
themeMode: context.watch<SettingsChangeProvider>().themeMode, themeMode: context.watch<SettingsChangeProvider>().themeMode,
initialRoute: '/', initialRoute: '/',
routes: { routes: {
"/": (context) => AppMainWidget(initialPage: 1), '/': (context) => const AppMainWidget(initialPage: 1),
"/chats": (context) => AppMainWidget(initialPage: 0) '/chats': (context) => const AppMainWidget(initialPage: 0)
}, },
); );
}, },
@ -143,8 +143,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
class AppMainWidget extends StatefulWidget { class AppMainWidget extends StatefulWidget {
const AppMainWidget({ const AppMainWidget({
super.key,
required this.initialPage, required this.initialPage,
super.key,
}); });
final int initialPage; final int initialPage;
@override @override
@ -182,7 +182,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
); );
}, },
), ),
AppOutdated(), const AppOutdated(),
], ],
); );
} }

View file

@ -103,8 +103,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
Future<void> updateContact( Future<void> updateContact(
int userId, ContactsCompanion updatedValues) async { int userId, ContactsCompanion updatedValues) async {
await ((update(contacts)..where((c) => c.userId.equals(userId))) await (update(contacts)..where((c) => c.userId.equals(userId)))
.write(updatedValues)); .write(updatedValues);
if (updatedValues.blocked.present || if (updatedValues.blocked.present ||
updatedValues.displayName.present || updatedValues.displayName.present ||
updatedValues.nickName.present) { updatedValues.nickName.present) {
@ -177,8 +177,9 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
Stream<int?> watchContactsBlocked() { Stream<int?> watchContactsBlocked() {
final count = contacts.userId.count(); final count = contacts.userId.count();
final query = selectOnly(contacts)..where(contacts.blocked.equals(true)); final query = selectOnly(contacts)
query.addColumns([count]); ..where(contacts.blocked.equals(true))
..addColumns([count]);
return query.map((row) => row.read(count)).watchSingle(); return query.map((row) => row.read(count)).watchSingle();
} }
@ -186,8 +187,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
final count = contacts.requested.count(distinct: true); final count = contacts.requested.count(distinct: true);
final query = selectOnly(contacts) final query = selectOnly(contacts)
..where(contacts.requested.equals(true) & ..where(contacts.requested.equals(true) &
contacts.accepted.equals(true).not()); contacts.accepted.equals(true).not())
query.addColumns([count]); ..addColumns([count]);
return query.map((row) => row.read(count)).watchSingle(); return query.map((row) => row.read(count)).watchSingle();
} }
@ -204,15 +205,13 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
u.lastMessageSend.isNotNull(), u.lastMessageSend.isNotNull(),
)) ))
.watchSingle() .watchSingle()
.asyncMap((contact) { .asyncMap(getFlameCounterFromContact);
return getFlameCounterFromContact(contact);
});
} }
} }
String getContactDisplayName(Contact user) { String getContactDisplayName(Contact user) {
String name = user.username; var name = user.username;
if (user.nickName != null && user.nickName != "") { if (user.nickName != null && user.nickName != '') {
name = user.nickName!; name = user.nickName!;
} else if (user.displayName != null) { } else if (user.displayName != null) {
name = user.displayName!; name = user.displayName!;
@ -224,7 +223,7 @@ String getContactDisplayName(Contact user) {
} }
String applyStrikethrough(String text) { String applyStrikethrough(String text) {
return text.split('').map((char) => '$char\u0336').join(''); return text.split('').map((char) => '$char\u0336').join();
} }
int getFlameCounterFromContact(Contact contact) { int getFlameCounterFromContact(Contact contact) {
@ -233,7 +232,7 @@ int getFlameCounterFromContact(Contact contact) {
} }
final now = DateTime.now(); final now = DateTime.now();
final startOfToday = DateTime(now.year, now.month, now.day); final startOfToday = DateTime(now.year, now.month, now.day);
final twoDaysAgo = startOfToday.subtract(Duration(days: 2)); final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
if (contact.lastMessageSend!.isAfter(twoDaysAgo) && if (contact.lastMessageSend!.isAfter(twoDaysAgo) &&
contact.lastMessageReceived!.isAfter(twoDaysAgo)) { contact.lastMessageReceived!.isAfter(twoDaysAgo)) {
return contact.flameCounter + 1; return contact.flameCounter + 1;

View file

@ -21,7 +21,7 @@ class MediaDownloadsDao extends DatabaseAccessor<TwonlyDatabase>
try { try {
return await into(mediaDownloads).insert(values); return await into(mediaDownloads).insert(values);
} catch (e) { } catch (e) {
Log.error("Error while inserting media upload: $e"); Log.error('Error while inserting media upload: $e');
return null; return null;
} }
} }

View file

@ -28,7 +28,7 @@ class MediaUploadsDao extends DatabaseAccessor<TwonlyDatabase>
try { try {
return await into(mediaUploads).insert(values); return await into(mediaUploads).insert(values);
} catch (e) { } catch (e) {
Log.error("Error while inserting media upload: $e"); Log.error('Error while inserting media upload: $e');
return null; return null;
} }
} }

View file

@ -17,7 +17,7 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
try { try {
return await into(messageRetransmissions).insert(message); return await into(messageRetransmissions).insert(message);
} catch (e) { } catch (e) {
Log.error("Error while inserting message for retransmission: $e"); Log.error('Error while inserting message for retransmission: $e');
return null; return null;
} }
} }
@ -29,7 +29,7 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
.go(); .go();
if (countDeleted > 0) { if (countDeleted > 0) {
Log.info("Deleted $countDeleted faulty retransmissions"); Log.info('Deleted $countDeleted faulty retransmissions');
} }
return (await (select(messageRetransmissions) return (await (select(messageRetransmissions)

View file

@ -51,7 +51,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
(t.openedAt.isNull() | (t.openedAt.isNull() |
t.mediaStored.equals(true) | t.mediaStored.equals(true) |
t.openedAt.isBiggerThanValue( t.openedAt.isBiggerThanValue(
DateTime.now().subtract(Duration(days: 1))))) DateTime.now().subtract(const Duration(days: 1)))))
..orderBy([(t) => OrderingTerm.desc(t.sendAt)])) ..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
.watch(); .watch();
} }
@ -60,13 +60,13 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
return (update(messages) return (update(messages)
..where((t) => ..where((t) =>
(t.openedAt.isSmallerThanValue( (t.openedAt.isSmallerThanValue(
DateTime.now().subtract(Duration(days: 1)), DateTime.now().subtract(const Duration(days: 1)),
) | ) |
(t.sendAt.isSmallerThanValue( (t.sendAt.isSmallerThanValue(
DateTime.now().subtract(Duration(days: 1))) & DateTime.now().subtract(const Duration(days: 1))) &
t.errorWhileSending.equals(true))) & t.errorWhileSending.equals(true))) &
t.kind.equals(MessageKind.textMessage.name))) t.kind.equals(MessageKind.textMessage.name)))
.write(MessagesCompanion(contentJson: Value(null))); .write(const MessagesCompanion(contentJson: Value(null)));
} }
Future<void> handleMediaFilesOlderThan7Days() { Future<void> handleMediaFilesOlderThan7Days() {
@ -78,11 +78,11 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
t.messageOtherId.isNull() & t.messageOtherId.isNull() &
(t.sendAt.isSmallerThanValue( (t.sendAt.isSmallerThanValue(
DateTime.now().subtract( DateTime.now().subtract(
Duration(days: 8), const Duration(days: 8),
), ),
))), ))),
)) ))
.write(MessagesCompanion(errorWhileSending: Value(true))); .write(const MessagesCompanion(errorWhileSending: Value(true)));
} }
Future<List<Message>> getAllMessagesPendingDownloading() { Future<List<Message>> getAllMessagesPendingDownloading() {
@ -104,7 +104,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
t.messageOtherId.isNull() & t.messageOtherId.isNull() &
t.errorWhileSending.equals(false) & t.errorWhileSending.equals(false) &
t.sendAt.isBiggerThanValue( t.sendAt.isBiggerThanValue(
DateTime.now().subtract(Duration(minutes: 10)), DateTime.now().subtract(const Duration(minutes: 10)),
))) )))
.get(); .get();
} }
@ -142,10 +142,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
} }
Future<void> resetPendingDownloadState() { Future<void> resetPendingDownloadState() {
// All media files in the downloading state are reseteded to the pending state // All media files in the downloading state are reset to the pending state
// When the app is used in mobile network, they will not be downloaded at the start // When the app is used in mobile network, they will not be downloaded at the start
// if they are not yet downloaded... // if they are not yet downloaded...
final updates = const updates =
MessagesCompanion(downloadState: Value(DownloadState.pending)); MessagesCompanion(downloadState: Value(DownloadState.pending));
return (update(messages) return (update(messages)
..where((t) => ..where((t) =>
@ -198,7 +198,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
return await into(messages).insert(message); return await into(messages).insert(message);
} catch (e) { } catch (e) {
Log.error("Error while inserting message: $e"); Log.error('Error while inserting message: $e');
return null; return null;
} }
} }
@ -244,7 +244,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
} }
Future<List<Message>> getMessagesByMediaUploadId(int mediaUploadId) async { Future<List<Message>> getMessagesByMediaUploadId(int mediaUploadId) async {
return await (select(messages) return (select(messages)
..where((t) => t.mediaUploadId.equals(mediaUploadId))) ..where((t) => t.mediaUploadId.equals(mediaUploadId)))
.get(); .get();
} }

View file

@ -62,9 +62,9 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
List<SignalContactPreKeysCompanion> preKeys) async { List<SignalContactPreKeysCompanion> preKeys) async {
for (final preKey in preKeys) { for (final preKey in preKeys) {
try { try {
into(signalContactPreKeys).insert(preKey); await into(signalContactPreKeys).insert(preKey);
} catch (e) { } catch (e) {
Log.error("$e"); Log.error('$e');
} }
} }
} }
@ -90,7 +90,7 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
await (delete(signalContactSignedPreKeys) await (delete(signalContactSignedPreKeys)
..where((t) => (t.createdAt.isSmallerThanValue( ..where((t) => (t.createdAt.isSmallerThanValue(
DateTime.now().subtract( DateTime.now().subtract(
Duration(days: 25), const Duration(days: 25),
), ),
)))) ))))
.go(); .go();
@ -98,7 +98,7 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
await (delete(twonlyDB.signalPreKeyStores) await (delete(twonlyDB.signalPreKeyStores)
..where((t) => (t.createdAt.isSmallerThanValue( ..where((t) => (t.createdAt.isSmallerThanValue(
DateTime.now().subtract( DateTime.now().subtract(
Duration(days: 40), const Duration(days: 40),
), ),
)))) ))))
.go(); .go();

View file

@ -1,13 +1,12 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.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:libsignal_protocol_dart/libsignal_protocol_dart.dart';
class ConnectSenderKeyStore extends SenderKeyStore { class ConnectSenderKeyStore extends SenderKeyStore {
@override @override
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async { Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
SignalSenderKeyStore? identity = final identity = await (twonlyDB.select(twonlyDB.signalSenderKeyStores)
await (twonlyDB.select(twonlyDB.signalSenderKeyStores)
..where((t) => t.senderKeyName.equals(senderKeyName.serialize()))) ..where((t) => t.senderKeyName.equals(senderKeyName.serialize())))
.getSingleOrNull(); .getSingleOrNull();
if (identity == null) { if (identity == null) {

View file

@ -1,6 +1,6 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:twonly/globals.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
class ConnectSessionStore extends SessionStore { class ConnectSessionStore extends SessionStore {
@ -50,7 +50,7 @@ class ConnectSessionStore extends SessionStore {
if (dbSession.isEmpty) { if (dbSession.isEmpty) {
return SessionRecord(); return SessionRecord();
} }
Uint8List session = dbSession.first.sessionRecord; final session = dbSession.first.sessionRecord;
return SessionRecord.fromSerialized(session); return SessionRecord.fromSerialized(session);
} }

View file

@ -1,8 +1,8 @@
import 'package:twonly/src/database/signal/connect_identitiy_key_store.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/database/signal/connect_identity_key_store.dart';
import 'package:twonly/src/database/signal/connect_pre_key_store.dart'; import 'package:twonly/src/database/signal/connect_pre_key_store.dart';
import 'package:twonly/src/database/signal/connect_session_store.dart'; import 'package:twonly/src/database/signal/connect_session_store.dart';
import 'package:twonly/src/database/signal/connect_signed_pre_key_store.dart'; import 'package:twonly/src/database/signal/connect_signed_pre_key_store.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
class ConnectSignalProtocolStore implements SignalProtocolStore { class ConnectSignalProtocolStore implements SignalProtocolStore {
ConnectSignalProtocolStore( ConnectSignalProtocolStore(

View file

@ -8,24 +8,25 @@ class Contacts extends Table {
TextColumn get nickName => text().nullable()(); TextColumn get nickName => text().nullable()();
TextColumn get avatarSvg => text().nullable()(); TextColumn get avatarSvg => text().nullable()();
IntColumn get myAvatarCounter => integer().withDefault(Constant(0))(); IntColumn get myAvatarCounter => integer().withDefault(const Constant(0))();
BoolColumn get accepted => boolean().withDefault(Constant(false))(); BoolColumn get accepted => boolean().withDefault(const Constant(false))();
BoolColumn get requested => boolean().withDefault(Constant(false))(); BoolColumn get requested => boolean().withDefault(const Constant(false))();
BoolColumn get blocked => boolean().withDefault(Constant(false))(); BoolColumn get blocked => boolean().withDefault(const Constant(false))();
BoolColumn get verified => boolean().withDefault(Constant(false))(); BoolColumn get verified => boolean().withDefault(const Constant(false))();
BoolColumn get archived => boolean().withDefault(Constant(false))(); BoolColumn get archived => boolean().withDefault(const Constant(false))();
BoolColumn get pinned => boolean().withDefault(Constant(false))(); BoolColumn get pinned => boolean().withDefault(const Constant(false))();
BoolColumn get deleted => boolean().withDefault(Constant(false))(); BoolColumn get deleted => boolean().withDefault(const Constant(false))();
BoolColumn get alsoBestFriend => boolean().withDefault(Constant(false))(); BoolColumn get alsoBestFriend =>
boolean().withDefault(const Constant(false))();
IntColumn get deleteMessagesAfterXMinutes => IntColumn get deleteMessagesAfterXMinutes =>
integer().withDefault(Constant(60 * 24))(); integer().withDefault(const Constant(60 * 24))();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
IntColumn get totalMediaCounter => integer().withDefault(Constant(0))(); IntColumn get totalMediaCounter => integer().withDefault(const Constant(0))();
DateTimeColumn get lastMessageSend => dateTime().nullable()(); DateTimeColumn get lastMessageSend => dateTime().nullable()();
DateTimeColumn get lastMessageReceived => dateTime().nullable()(); DateTimeColumn get lastMessageReceived => dateTime().nullable()();
@ -34,7 +35,7 @@ class Contacts extends Table {
DateTimeColumn get lastMessageExchange => DateTimeColumn get lastMessageExchange =>
dateTime().withDefault(currentDateAndTime)(); dateTime().withDefault(currentDateAndTime)();
IntColumn get flameCounter => integer().withDefault(Constant(0))(); IntColumn get flameCounter => integer().withDefault(const Constant(0))();
@override @override
Set<Column> get primaryKey => {userId}; Set<Column> get primaryKey => {userId};

View file

@ -44,16 +44,18 @@ class Messages extends Table {
IntColumn get responseToMessageId => integer().nullable()(); IntColumn get responseToMessageId => integer().nullable()();
IntColumn get responseToOtherMessageId => integer().nullable()(); IntColumn get responseToOtherMessageId => integer().nullable()();
BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))(); BoolColumn get acknowledgeByUser =>
BoolColumn get mediaStored => boolean().withDefault(Constant(false))(); boolean().withDefault(const Constant(false))();
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
IntColumn get downloadState => intEnum<DownloadState>() IntColumn get downloadState => intEnum<DownloadState>()
.withDefault(Constant(DownloadState.downloaded.index))(); .withDefault(Constant(DownloadState.downloaded.index))();
BoolColumn get acknowledgeByServer => BoolColumn get acknowledgeByServer =>
boolean().withDefault(Constant(false))(); boolean().withDefault(const Constant(false))();
BoolColumn get errorWhileSending => boolean().withDefault(Constant(false))(); BoolColumn get errorWhileSending =>
boolean().withDefault(const Constant(false))();
TextColumn get mediaRetransmissionState => textEnum<MediaRetransmitting>() TextColumn get mediaRetransmissionState => textEnum<MediaRetransmitting>()
.withDefault(Constant(MediaRetransmitting.none.name))(); .withDefault(Constant(MediaRetransmitting.none.name))();

View file

@ -1,6 +1,8 @@
import 'dart:typed_data';
import 'package:json_annotation/json_annotation.dart';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:json_annotation/json_annotation.dart';
part 'signal_identity.g.dart'; part 'signal_identity.g.dart';
@JsonSerializable() @JsonSerializable()
@ -9,13 +11,13 @@ class SignalIdentity {
required this.identityKeyPairU8List, required this.identityKeyPairU8List,
required this.registrationId, required this.registrationId,
}); });
factory SignalIdentity.fromJson(Map<String, dynamic> json) =>
_$SignalIdentityFromJson(json);
final int registrationId; final int registrationId;
@Uint8ListConverter() @Uint8ListConverter()
final Uint8List identityKeyPairU8List; final Uint8List identityKeyPairU8List;
factory SignalIdentity.fromJson(Map<String, dynamic> json) =>
_$SignalIdentityFromJson(json);
Map<String, dynamic> toJson() => _$SignalIdentityToJson(this); Map<String, dynamic> toJson() => _$SignalIdentityToJson(this);
} }

View file

@ -11,6 +11,8 @@ class UserData {
required this.subscriptionPlan, required this.subscriptionPlan,
required this.isDemoUser, required this.isDemoUser,
}); });
factory UserData.fromJson(Map<String, dynamic> json) =>
_$UserDataFromJson(json);
final int userId; final int userId;
@ -29,7 +31,7 @@ class UserData {
// --- SUBSCRIPTION DTA --- // --- SUBSCRIPTION DTA ---
@JsonKey(defaultValue: "Preview") @JsonKey(defaultValue: 'Preview')
String subscriptionPlan; String subscriptionPlan;
DateTime? lastImageSend; DateTime? lastImageSend;
int? todaysImageCounter; int? todaysImageCounter;
@ -81,9 +83,6 @@ class UserData {
DateTime? nextTimeToShowBackupNotice; DateTime? nextTimeToShowBackupNotice;
BackupServer? backupServer; BackupServer? backupServer;
TwonlySafeBackup? twonlySafeBackup; TwonlySafeBackup? twonlySafeBackup;
factory UserData.fromJson(Map<String, dynamic> json) =>
_$UserDataFromJson(json);
Map<String, dynamic> toJson() => _$UserDataToJson(this); Map<String, dynamic> toJson() => _$UserDataToJson(this);
} }
@ -95,15 +94,14 @@ class TwonlySafeBackup {
required this.backupId, required this.backupId,
required this.encryptionKey, required this.encryptionKey,
}); });
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) =>
_$TwonlySafeBackupFromJson(json);
int lastBackupSize = 0; int lastBackupSize = 0;
LastBackupUploadState backupUploadState = LastBackupUploadState.none; LastBackupUploadState backupUploadState = LastBackupUploadState.none;
DateTime? lastBackupDone; DateTime? lastBackupDone;
List<int> backupId; List<int> backupId;
List<int> encryptionKey; List<int> encryptionKey;
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) =>
_$TwonlySafeBackupFromJson(json);
Map<String, dynamic> toJson() => _$TwonlySafeBackupToJson(this); Map<String, dynamic> toJson() => _$TwonlySafeBackupToJson(this);
} }
@ -114,12 +112,11 @@ class BackupServer {
required this.retentionDays, required this.retentionDays,
required this.maxBackupBytes, required this.maxBackupBytes,
}); });
factory BackupServer.fromJson(Map<String, dynamic> json) =>
_$BackupServerFromJson(json);
String serverUrl; String serverUrl;
int retentionDays; int retentionDays;
int maxBackupBytes; int maxBackupBytes;
factory BackupServer.fromJson(Map<String, dynamic> json) =>
_$BackupServerFromJson(json);
Map<String, dynamic> toJson() => _$BackupServerToJson(this); Map<String, dynamic> toJson() => _$BackupServerToJson(this);
} }

View file

@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
bool _isConnected = false; bool _isConnected = false;
bool get isConnected => _isConnected; bool get isConnected => _isConnected;
String plan = "Preview"; String plan = 'Preview';
Future<void> updateConnectionState(bool update) async { Future<void> updateConnectionState(bool update) async {
_isConnected = update; _isConnected = update;
notifyListeners(); notifyListeners();

View file

@ -61,12 +61,13 @@ class ApiService {
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap(); final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
IOWebSocketChannel? _channel; IOWebSocketChannel? _channel;
// ignore: cancel_subscriptions
StreamSubscription<List<ConnectivityResult>>? connectivitySubscription; StreamSubscription<List<ConnectivityResult>>? connectivitySubscription;
Future<bool> _connectTo(String apiUrl) async { Future<bool> _connectTo(String apiUrl) async {
if (appIsOutdated) return false; if (appIsOutdated) return false;
try { try {
var channel = IOWebSocketChannel.connect( final channel = IOWebSocketChannel.connect(
Uri.parse(apiUrl), Uri.parse(apiUrl),
); );
_channel = channel; _channel = channel;
@ -195,11 +196,11 @@ class ApiService {
onClosed(); onClosed();
} }
void _onData(dynamic msgBuffer) async { Future<void> _onData(dynamic msgBuffer) async {
try { try {
final msg = server.ServerToClient.fromBuffer(msgBuffer as Uint8List); final msg = server.ServerToClient.fromBuffer(msgBuffer as Uint8List);
if (msg.v0.hasResponse()) { if (msg.v0.hasResponse()) {
removeFromRetransmissionBuffer(msg.v0.seq); await removeFromRetransmissionBuffer(msg.v0.seq);
messagesV0[msg.v0.seq] = msg; messagesV0[msg.v0.seq] = msg;
} else { } else {
await handleServerMessage(msg); await handleServerMessage(msg);
@ -212,7 +213,7 @@ class ApiService {
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async { Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
final startTime = DateTime.now(); final startTime = DateTime.now();
final timeout = Duration(seconds: 20); const timeout = Duration(seconds: 20);
while (true) { while (true) {
if (messagesV0[seq] != null) { if (messagesV0[seq] != null) {
@ -393,7 +394,7 @@ class ApiService {
return; return;
} }
var handshake = Handshake() final handshake = Handshake()
..getauthchallenge = Handshake_GetAuthChallenge(); ..getauthchallenge = Handshake_GetAuthChallenge();
final req = createClientToServerFromHandshake(handshake); final req = createClientToServerFromHandshake(handshake);
@ -445,7 +446,7 @@ class ApiService {
final signedPreKey = (await signalStore.loadSignedPreKeys())[0]; final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
var register = Handshake_Register() final register = Handshake_Register()
..username = username ..username = username
..publicIdentityKey = ..publicIdentityKey =
(await signalStore.getIdentityKeyPair()).getPublicKey().serialize() (await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
@ -614,7 +615,7 @@ class ApiService {
..userId = Int64(userId); ..userId = Int64(userId);
final appData = ApplicationData()..getsignedprekeybyuserid = get; final appData = ApplicationData()..getsignedprekeybyuserid = get;
final req = createClientToServerFromApplicationData(appData); final req = createClientToServerFromApplicationData(appData);
Result res = await sendRequestSync(req, contactId: userId); final res = await sendRequestSync(req, contactId: userId);
if (res.isSuccess) { if (res.isSuccess) {
final ok = res.value as server.Response_Ok; final ok = res.value as server.Response_Ok;
if (ok.hasSignedprekey()) { if (ok.hasSignedprekey()) {
@ -628,11 +629,11 @@ class ApiService {
final get = ApplicationData_GetPrekeysByUserId()..userId = Int64(userId); final get = ApplicationData_GetPrekeysByUserId()..userId = Int64(userId);
final appData = ApplicationData()..getprekeysbyuserid = get; final appData = ApplicationData()..getprekeysbyuserid = get;
final req = createClientToServerFromApplicationData(appData); final req = createClientToServerFromApplicationData(appData);
Result res = await sendRequestSync(req, contactId: userId); final res = await sendRequestSync(req, contactId: userId);
if (res.isSuccess) { if (res.isSuccess) {
final ok = res.value as server.Response_Ok; final ok = res.value as server.Response_Ok;
if (ok.hasUserdata()) { if (ok.hasUserdata()) {
server.Response_UserData data = ok.userdata; final data = ok.userdata;
if (data.hasSignedPrekey() && if (data.hasSignedPrekey() &&
data.hasSignedPrekeyId() && data.hasSignedPrekeyId() &&
data.hasSignedPrekeySignature()) { data.hasSignedPrekeySignature()) {
@ -650,7 +651,7 @@ class ApiService {
Future<Result> sendTextMessage( Future<Result> sendTextMessage(
int target, Uint8List msg, List<int>? pushData) async { int target, Uint8List msg, List<int>? pushData) async {
var testMessage = ApplicationData_TextMessage() final testMessage = ApplicationData_TextMessage()
..userId = Int64(target) ..userId = Int64(target)
..body = msg; ..body = msg;

View file

@ -114,7 +114,7 @@ Future<void> initFileDownloader() async {
Future<bool> checkForFailedUploads() async { Future<bool> checkForFailedUploads() async {
final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload(); final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload();
final mediaUploadIds = <int>[]; final mediaUploadIds = <int>[];
for (var message in messages) { for (final message in messages) {
if (mediaUploadIds.contains(message.mediaUploadId)) { if (mediaUploadIds.contains(message.mediaUploadId)) {
continue; continue;
} }
@ -690,7 +690,7 @@ Future<void> handleUploadWhenAppGoesBackground() async {
Log.info('App goes into background. Enqueue uploads to the background.'); Log.info('App goes into background. Enqueue uploads to the background.');
final keys = currentUploadTasks.keys.toList(); final keys = currentUploadTasks.keys.toList();
for (final key in keys) { for (final key in keys) {
enqueueUploadTask(key); await enqueueUploadTask(key);
} }
} }

View file

@ -1,3 +1,5 @@
// ignore_for_file: avoid_dynamic_calls
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';

View file

@ -37,8 +37,8 @@ Result asResult(server.ServerToClient? msg) {
} }
ClientToServer createClientToServerFromHandshake(Handshake handshake) { ClientToServer createClientToServerFromHandshake(Handshake handshake) {
var v0 = client.V0() final v0 = client.V0()
..seq = Int64(0) ..seq = Int64()
..handshake = handshake; ..handshake = handshake;
return ClientToServer()..v0 = v0; return ClientToServer()..v0 = v0;
} }
@ -46,7 +46,7 @@ ClientToServer createClientToServerFromHandshake(Handshake handshake) {
ClientToServer createClientToServerFromApplicationData( ClientToServer createClientToServerFromApplicationData(
ApplicationData applicationData) { ApplicationData applicationData) {
final v0 = client.V0() final v0 = client.V0()
..seq = Int64(0) ..seq = Int64()
..applicationdata = applicationData; ..applicationdata = applicationData;
return ClientToServer()..v0 = v0; return ClientToServer()..v0 = v0;
} }

View file

@ -4,20 +4,19 @@ 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/message.dart' as my;
import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/messages.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/utils/storage.dart';
import 'package:twonly/src/model/json/message.dart' as my;
Future<void> syncFlameCounters() async { Future<void> syncFlameCounters() async {
var user = await getUser(); final user = await getUser();
if (user == null) return; if (user == null) return;
List<Contact> contacts = final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
await twonlyDB.contactsDao.getAllNotBlockedContacts();
if (contacts.isEmpty) return; if (contacts.isEmpty) return;
int maxMessageCounter = contacts.map((x) => x.totalMediaCounter).max; final maxMessageCounter = contacts.map((x) => x.totalMediaCounter).max;
Contact bestFriend = final bestFriend =
contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter); contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
if (user.myBestFriendContactId != bestFriend.userId) { if (user.myBestFriendContactId != bestFriend.userId) {
@ -27,13 +26,13 @@ Future<void> syncFlameCounters() async {
}); });
} }
for (Contact contact in contacts) { for (final contact in contacts) {
if (contact.lastFlameCounterChange == null) continue; if (contact.lastFlameCounterChange == null) continue;
if (contact.lastFlameSync != null) { if (contact.lastFlameSync != null) {
if (isToday(contact.lastFlameSync!)) continue; if (isToday(contact.lastFlameSync!)) continue;
} }
int flameCounter = getFlameCounterFromContact(contact) - 1; final flameCounter = getFlameCounterFromContact(contact) - 1;
// only sync when flame counter is higher than three days // only sync when flame counter is higher than three days
if (flameCounter < 1 && bestFriend.userId != contact.userId) continue; if (flameCounter < 1 && bestFriend.userId != contact.userId) continue;

View file

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -29,24 +28,18 @@ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
int id = 0; int id = 0;
Future<void> setupPushNotification() async { Future<void> setupPushNotification() async {
const AndroidInitializationSettings initializationSettingsAndroid = const initializationSettingsAndroid =
AndroidInitializationSettings("ic_launcher_foreground"); AndroidInitializationSettings('ic_launcher_foreground');
final List<DarwinNotificationCategory> darwinNotificationCategories = final darwinNotificationCategories = <DarwinNotificationCategory>[];
<DarwinNotificationCategory>[];
/// Note: permissions aren't requested here just to demonstrate that can be /// Note: permissions aren't requested here just to demonstrate that can be
/// done later /// done later
final DarwinInitializationSettings initializationSettingsDarwin = final initializationSettingsDarwin = DarwinInitializationSettings(
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
requestProvisionalPermission: false,
notificationCategories: darwinNotificationCategories, notificationCategories: darwinNotificationCategories,
); );
final InitializationSettings initializationSettings = InitializationSettings( final initializationSettings = InitializationSettings(
android: initializationSettingsAndroid, android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin, iOS: initializationSettingsDarwin,
); );
@ -65,23 +58,22 @@ Future<void> createPushAvatars() async {
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts(); final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
for (final contact in contacts) { for (final contact in contacts) {
if (contact.avatarSvg == null) return null; if (contact.avatarSvg == null) return;
final PictureInfo pictureInfo = final pictureInfo =
await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null); await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null);
final ui.Image image = await pictureInfo.picture.toImage(300, 300); final image = await pictureInfo.picture.toImage(300, 300);
final ByteData? byteData = final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
await image.toByteData(format: ui.ImageByteFormat.png); final pngBytes = byteData!.buffer.asUint8List();
final Uint8List pngBytes = byteData!.buffer.asUint8List();
// Get the directory to save the image // Get the directory to save the image
final directory = await getApplicationCacheDirectory(); final directory = await getApplicationCacheDirectory();
final avatarsDirectory = Directory('${directory.path}/avatars'); final avatarsDirectory = Directory('${directory.path}/avatars');
// Create the avatars directory if it does not exist // Create the avatars directory if it does not exist
if (!await avatarsDirectory.exists()) { if (!avatarsDirectory.existsSync()) {
await avatarsDirectory.create(recursive: true); await avatarsDirectory.create(recursive: true);
} }
final filePath = '${avatarsDirectory.path}/${contact.userId}.png'; final filePath = '${avatarsDirectory.path}/${contact.userId}.png';

View file

@ -43,18 +43,17 @@ Future<Uint8List?> signalEncryptMessage(
); );
} }
final ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint( final tempSignedPreKeyPublic = Curve.decodePoint(
DjbECPublicKey(Uint8List.fromList(signedPreKey.signedPreKey)) DjbECPublicKey(Uint8List.fromList(signedPreKey.signedPreKey))
.serialize(), .serialize(),
1, 1,
); );
final Uint8List? tempSignedPreKeySignature = Uint8List.fromList( final tempSignedPreKeySignature = Uint8List.fromList(
signedPreKey.signedPreKeySignature, signedPreKey.signedPreKeySignature,
); );
final IdentityKey? tempIdentityKey = final tempIdentityKey = await signalStore.getIdentity(address);
await signalStore.getIdentity(address);
if (tempIdentityKey != null) { if (tempIdentityKey != null) {
final preKeyBundle = PreKeyBundle( final preKeyBundle = PreKeyBundle(
target, target,
@ -79,9 +78,9 @@ Future<Uint8List?> signalEncryptMessage(
final ciphertext = await session.encrypt(plaintextContent); final ciphertext = await session.encrypt(plaintextContent);
final b = BytesBuilder(); final b = BytesBuilder()
b.add(ciphertext.serialize()); ..add(ciphertext.serialize())
b.add(intToBytes(ciphertext.getType())); ..add(intToBytes(ciphertext.getType()));
return b.takeBytes(); return b.takeBytes();
} catch (e) { } catch (e) {

View file

@ -1,10 +1,12 @@
import 'dart:async';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:mutex/mutex.dart'; import 'package:mutex/mutex.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/utils/log.dart';
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
as server; as server;
import 'package:twonly/src/utils/log.dart';
class OtherPreKeys { class OtherPreKeys {
OtherPreKeys({ OtherPreKeys({
@ -20,21 +22,22 @@ class OtherPreKeys {
} }
Mutex requestNewKeys = Mutex(); Mutex requestNewKeys = Mutex();
DateTime lastPreKeyRequest = DateTime.now().subtract(Duration(hours: 1)); DateTime lastPreKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
DateTime lastSignedPreKeyRequest = DateTime.now().subtract(Duration(hours: 1)); DateTime lastSignedPreKeyRequest =
DateTime.now().subtract(const Duration(hours: 1));
Future<void> requestNewPrekeysForContact(int contactId) async { Future<void> requestNewPrekeysForContact(int contactId) async {
if (lastPreKeyRequest if (lastPreKeyRequest
.isAfter(DateTime.now().subtract(Duration(seconds: 60)))) { .isAfter(DateTime.now().subtract(const Duration(seconds: 60)))) {
return; return;
} }
Log.info("Requesting new PREKEYS for $contactId"); Log.info('Requesting new PREKEYS for $contactId');
lastPreKeyRequest = DateTime.now(); lastPreKeyRequest = DateTime.now();
requestNewKeys.protect(() async { await requestNewKeys.protect(() async {
final otherKeys = await apiService.getPreKeysByUserId(contactId); final otherKeys = await apiService.getPreKeysByUserId(contactId);
if (otherKeys != null) { if (otherKeys != null) {
Log.info( Log.info(
"got fresh ${otherKeys.preKeys.length} pre keys from other $contactId!"); 'got fresh ${otherKeys.preKeys.length} pre keys from other $contactId!');
final preKeys = otherKeys.preKeys final preKeys = otherKeys.preKeys
.map( .map(
(preKey) => SignalContactPreKeysCompanion( (preKey) => SignalContactPreKeysCompanion(
@ -46,30 +49,30 @@ Future<void> requestNewPrekeysForContact(int contactId) async {
.toList(); .toList();
await twonlyDB.signalDao.insertPreKeys(preKeys); await twonlyDB.signalDao.insertPreKeys(preKeys);
} else { } else {
Log.error("could not load new pre keys for user $contactId"); Log.error('could not load new pre keys for user $contactId');
} }
}); });
} }
Future<SignalContactPreKey?> getPreKeyByContactId(int contactId) async { Future<SignalContactPreKey?> getPreKeyByContactId(int contactId) async {
int count = await twonlyDB.signalDao.countPreKeysByContactId(contactId); final count = await twonlyDB.signalDao.countPreKeysByContactId(contactId);
if (count < 10) { if (count < 10) {
requestNewPrekeysForContact(contactId); unawaited(requestNewPrekeysForContact(contactId));
} }
return twonlyDB.signalDao.popPreKeyByContactId(contactId); return twonlyDB.signalDao.popPreKeyByContactId(contactId);
} }
Future<void> requestNewSignedPreKeyForContact(int contactId) async { Future<void> requestNewSignedPreKeyForContact(int contactId) async {
if (lastSignedPreKeyRequest if (lastSignedPreKeyRequest
.isAfter(DateTime.now().subtract(Duration(seconds: 60)))) { .isAfter(DateTime.now().subtract(const Duration(seconds: 60)))) {
Log.info("last signed pre request was 60s before"); Log.info('last signed pre request was 60s before');
return; return;
} }
lastSignedPreKeyRequest = DateTime.now(); lastSignedPreKeyRequest = DateTime.now();
await requestNewKeys.protect(() async { await requestNewKeys.protect(() async {
final signedPreKey = await apiService.getSignedKeyByUserId(contactId); final signedPreKey = await apiService.getSignedKeyByUserId(contactId);
if (signedPreKey != null) { if (signedPreKey != null) {
Log.info("got fresh signed pre keys from other $contactId!"); Log.info('got fresh signed pre keys from other $contactId!');
await twonlyDB.signalDao.insertOrUpdateSignedPreKeyByContactId( await twonlyDB.signalDao.insertOrUpdateSignedPreKeyByContactId(
SignalContactSignedPreKeysCompanion( SignalContactSignedPreKeysCompanion(
contactId: Value(contactId), contactId: Value(contactId),
@ -79,7 +82,7 @@ Future<void> requestNewSignedPreKeyForContact(int contactId) async {
signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()), signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()),
)); ));
} else { } else {
Log.error("could not load new signed pre key for user $contactId"); Log.error('could not load new signed pre key for user $contactId');
} }
}); });
} }
@ -87,19 +90,20 @@ Future<void> requestNewSignedPreKeyForContact(int contactId) async {
Future<SignalContactSignedPreKey?> getSignedPreKeyByContactId( Future<SignalContactSignedPreKey?> getSignedPreKeyByContactId(
int contactId, int contactId,
) async { ) async {
SignalContactSignedPreKey? signedPreKey = final signedPreKey =
await twonlyDB.signalDao.getSignedPreKeyByContactId(contactId); await twonlyDB.signalDao.getSignedPreKeyByContactId(contactId);
if (signedPreKey != null) { if (signedPreKey != null) {
DateTime fortyEightHoursAgo = DateTime.now().subtract(Duration(hours: 48)); final fortyEightHoursAgo =
bool isOlderThan48Hours = DateTime.now().subtract(const Duration(hours: 48));
(signedPreKey.createdAt).isBefore(fortyEightHoursAgo); final isOlderThan48Hours =
signedPreKey.createdAt.isBefore(fortyEightHoursAgo);
if (isOlderThan48Hours) { if (isOlderThan48Hours) {
requestNewSignedPreKeyForContact(contactId); unawaited(requestNewSignedPreKeyForContact(contactId));
} }
} else { } else {
requestNewSignedPreKeyForContact(contactId); unawaited(requestNewSignedPreKeyForContact(contactId));
Log.error("Contact $contactId does not have a signed pre key!"); Log.error('Contact $contactId does not have a signed pre key!');
} }
return signedPreKey; return signedPreKey;
} }

View file

@ -1,26 +1,24 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/consts.signal.dart';
import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
import 'package:twonly/src/utils/storage.dart'; import 'package:twonly/src/utils/storage.dart';
Future<bool> createNewSignalSession(Response_UserData userData) async { Future<bool> createNewSignalSession(Response_UserData userData) async {
SignalProtocolStore? signalStore = await getSignalStore(); final SignalProtocolStore? signalStore = await getSignalStore();
if (signalStore == null) { if (signalStore == null) {
return false; return false;
} }
SignalProtocolAddress targetAddress = SignalProtocolAddress( final targetAddress = SignalProtocolAddress(
userData.userId.toString(), userData.userId.toString(),
defaultDeviceId, defaultDeviceId,
); );
SessionBuilder sessionBuilder = SessionBuilder.fromSignalStore( final sessionBuilder = SessionBuilder.fromSignalStore(
signalStore, signalStore,
targetAddress, targetAddress,
); );
@ -38,18 +36,18 @@ Future<bool> createNewSignalSession(Response_UserData userData) async {
tempPreKeyId = userData.prekeys.first.id.toInt(); tempPreKeyId = userData.prekeys.first.id.toInt();
} }
int tempSignedPreKeyId = userData.signedPrekeyId.toInt(); final tempSignedPreKeyId = userData.signedPrekeyId.toInt();
ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint( final tempSignedPreKeyPublic = Curve.decodePoint(
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(), DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(),
1, 1,
); );
Uint8List? tempSignedPreKeySignature = Uint8List.fromList( final tempSignedPreKeySignature = Uint8List.fromList(
userData.signedPrekeySignature, userData.signedPrekeySignature,
); );
IdentityKey tempIdentityKey = IdentityKey( final tempIdentityKey = IdentityKey(
Curve.decodePoint( Curve.decodePoint(
DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey)) DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey))
.serialize(), .serialize(),
@ -57,7 +55,7 @@ Future<bool> createNewSignalSession(Response_UserData userData) async {
), ),
); );
PreKeyBundle preKeyBundle = PreKeyBundle( final preKeyBundle = PreKeyBundle(
userData.userId.toInt(), userData.userId.toInt(),
defaultDeviceId, defaultDeviceId,
tempPreKeyId, tempPreKeyId,
@ -72,32 +70,32 @@ Future<bool> createNewSignalSession(Response_UserData userData) async {
await sessionBuilder.processPreKeyBundle(preKeyBundle); await sessionBuilder.processPreKeyBundle(preKeyBundle);
return true; return true;
} catch (e) { } catch (e) {
Log.error("could not process pre key bundle: $e"); Log.error('could not process pre key bundle: $e');
return false; return false;
} }
} }
Future<void> deleteSessionWithTarget(int target) async { Future<void> deleteSessionWithTarget(int target) async {
ConnectSignalProtocolStore? signalStore = await getSignalStore(); final signalStore = await getSignalStore();
if (signalStore == null) return; if (signalStore == null) return;
final address = SignalProtocolAddress(target.toString(), defaultDeviceId); final address = SignalProtocolAddress(target.toString(), defaultDeviceId);
await signalStore.sessionStore.deleteSession(address); await signalStore.sessionStore.deleteSession(address);
} }
Future<Fingerprint?> generateSessionFingerPrint(int target) async { Future<Fingerprint?> generateSessionFingerPrint(int target) async {
ConnectSignalProtocolStore? signalStore = await getSignalStore(); final signalStore = await getSignalStore();
UserData? user = await getUser(); final user = await getUser();
if (signalStore == null || user == null) return null; if (signalStore == null || user == null) return null;
try { try {
IdentityKey? targetIdentity = await signalStore final targetIdentity = await signalStore
.getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId)); .getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId));
if (targetIdentity != null) { if (targetIdentity != null) {
final generator = NumericFingerprintGenerator(5200); final generator = NumericFingerprintGenerator(5200);
final localFingerprint = generator.createFor( final localFingerprint = generator.createFor(
1, 1,
Uint8List.fromList([user.userId.toInt()]), Uint8List.fromList([user.userId]),
(await signalStore.getIdentityKeyPair()).getPublicKey(), (await signalStore.getIdentityKeyPair()).getPublicKey(),
Uint8List.fromList([target.toInt()]), Uint8List.fromList([target]),
targetIdentity, targetIdentity,
); );

View file

@ -1,17 +1,19 @@
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/model/json/signal_identity.dart';
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart'; import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
import 'package:twonly/src/model/json/signal_identity.dart';
import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/services/signal/identity.signal.dart';
Future<ConnectSignalProtocolStore?> getSignalStore() async { Future<ConnectSignalProtocolStore?> getSignalStore() async {
return await getSignalStoreFromIdentity((await getSignalIdentity())!); return getSignalStoreFromIdentity((await getSignalIdentity())!);
} }
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity( Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
SignalIdentity signalIdentity) async { SignalIdentity signalIdentity) async {
final IdentityKeyPair identityKeyPair = final identityKeyPair =
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List); IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
return ConnectSignalProtocolStore( return ConnectSignalProtocolStore(
identityKeyPair, signalIdentity.registrationId.toInt()); identityKeyPair,
signalIdentity.registrationId,
);
} }

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:hashlib/hashlib.dart'; import 'package:hashlib/hashlib.dart';
@ -21,7 +22,7 @@ Future<void> enableTwonlySafe(String password) async {
); );
return user; return user;
}); });
performTwonlySafeBackup(force: true); unawaited(performTwonlySafeBackup(force: true));
} }
Future<void> disableTwonlySafe() async { Future<void> disableTwonlySafe() async {
@ -35,9 +36,9 @@ Future<void> disableTwonlySafe() async {
// Add any other headers if required // Add any other headers if required
}, },
); );
Log.info("Download deleted with: ${response.statusCode}"); Log.info('Download deleted with: ${response.statusCode}');
} catch (e) { } catch (e) {
Log.error("Could not connect to the server."); Log.error('Could not connect to the server.');
} }
} }
await updateUserdata((user) { await updateUserdata((user) {
@ -50,21 +51,18 @@ Future<(Uint8List, Uint8List)> getMasterKey(
String password, String password,
String username, String username,
) async { ) async {
List<int> passwordBytes = utf8.encode(password); final List<int> passwordBytes = utf8.encode(password);
List<int> saltBytes = utf8.encode(username); final List<int> saltBytes = utf8.encode(username);
// Values are derived from the Threema Whitepaper // Values are derived from the Threema Whitepaper
// https://threema.com/assets/documents/cryptography_whitepaper.pdf // https://threema.com/assets/documents/cryptography_whitepaper.pdf
final scrypt = Scrypt( final scrypt = Scrypt(
cost: 65536, cost: 65536,
blockSize: 8,
parallelism: 1,
derivedKeyLength: 64,
salt: saltBytes, salt: saltBytes,
); );
final key = (scrypt.convert(passwordBytes)).bytes; final key = scrypt.convert(passwordBytes).bytes;
return (key.sublist(0, 32), key.sublist(32, 64)); return (key.sublist(0, 32), key.sublist(32, 64));
} }
@ -81,13 +79,13 @@ Future<String?> getTwonlySafeBackupUrlFromServer(
List<int> backupId, List<int> backupId,
BackupServer? backupServer, BackupServer? backupServer,
) async { ) async {
String backupServerUrl = "https://safe.twonly.eu/"; var backupServerUrl = 'https://safe.twonly.eu/';
if (backupServer != null) { if (backupServer != null) {
backupServerUrl = backupServer.serverUrl; backupServerUrl = backupServer.serverUrl;
} }
String backupIdHex = uint8ListToHex(backupId).toLowerCase(); final backupIdHex = uint8ListToHex(backupId).toLowerCase();
return "${backupServerUrl}backups/$backupIdHex"; return '${backupServerUrl}backups/$backupIdHex';
} }

View file

@ -1,3 +1,5 @@
// ignore_for_file: parameter_assignments
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:background_downloader/background_downloader.dart'; import 'package:background_downloader/background_downloader.dart';
@ -31,8 +33,8 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
return; return;
} }
final DateTime? lastUpdateTime = user.twonlySafeBackup!.lastBackupDone; final lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
if (!force && lastUpdateTime != null) { if (force != true && lastUpdateTime != null) {
if (lastUpdateTime if (lastUpdateTime
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) { .isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
return; return;
@ -179,8 +181,6 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
file: encryptedBackupBytesFile, file: encryptedBackupBytesFile,
httpRequestMethod: 'PUT', httpRequestMethod: 'PUT',
url: (await getTwonlySafeBackupUrl())!, url: (await getTwonlySafeBackupUrl())!,
// requiresWiFi: true,
priority: 5,
post: 'binary', post: 'binary',
retries: 2, retries: 2,
headers: { headers: {

View file

@ -36,8 +36,8 @@ Future<String> loadLogFile() async {
final directory = await getApplicationSupportDirectory(); final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log'); final logFile = File('${directory.path}/app.log');
if (await logFile.exists()) { if (logFile.existsSync()) {
return await logFile.readAsString(); return logFile.readAsString();
} else { } else {
return 'Log file does not exist.'; return 'Log file does not exist.';
} }
@ -51,7 +51,7 @@ Future<void> _writeLogToFile(LogRecord record) async {
final logMessage = final logMessage =
'${DateTime.now().toString().split(".")[0]} ${record.level.name} [twonly] ${record.loggerName} > ${record.message}\n'; '${DateTime.now().toString().split(".")[0]} ${record.level.name} [twonly] ${record.loggerName} > ${record.message}\n';
writeToLogGuard.protect(() async { await writeToLogGuard.protect(() async {
// Append the log message to the file // Append the log message to the file
await logFile.writeAsString(logMessage, mode: FileMode.append); await logFile.writeAsString(logMessage, mode: FileMode.append);
}); });
@ -61,7 +61,7 @@ Future<bool> deleteLogFile() async {
final directory = await getApplicationSupportDirectory(); final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log'); final logFile = File('${directory.path}/app.log');
if (await logFile.exists()) { if (logFile.existsSync()) {
await logFile.delete(); await logFile.delete();
return true; return true;
} }
@ -69,18 +69,18 @@ Future<bool> deleteLogFile() async {
} }
String _getCallerSourceCodeFilename() { String _getCallerSourceCodeFilename() {
StackTrace stackTrace = StackTrace.current; final stackTrace = StackTrace.current;
String stackTraceString = stackTrace.toString(); final stackTraceString = stackTrace.toString();
String fileName = ""; var fileName = '';
String lineNumber = ""; var lineNumber = '';
List<String> stackLines = stackTraceString.split('\n'); final stackLines = stackTraceString.split('\n');
if (stackLines.length > 2) { if (stackLines.length > 2) {
String callerInfo = stackLines[2]; final callerInfo = stackLines[2];
List<String> parts = callerInfo.split('/'); final parts = callerInfo.split('/');
fileName = parts.last.split(':').first; // Extract the file name fileName = parts.last.split(':').first; // Extract the file name
lineNumber = parts.last.split(':')[1]; // Extract the line number lineNumber = parts.last.split(':')[1]; // Extract the line number
} else { } else {
String firstLine = stackTraceString.split('\n')[0]; final firstLine = stackTraceString.split('\n')[0];
fileName = fileName =
firstLine.split('/').last.split(':').first; // Extract the file name firstLine.split('/').last.split(':').first; // Extract the file name
lineNumber = firstLine.split(':')[1]; // Extract the line number lineNumber = firstLine.split(':')[1]; // Extract the line number

View file

@ -1,9 +1,11 @@
// ignore_for_file: avoid_dynamic_calls
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
class PermissionHandlerView extends StatefulWidget { class PermissionHandlerView extends StatefulWidget {
const PermissionHandlerView({super.key, required this.onSuccess}); const PermissionHandlerView({required this.onSuccess, super.key});
final Function onSuccess; final Function onSuccess;
@ -25,7 +27,7 @@ Future<bool> checkPermissions() async {
class PermissionHandlerViewState extends State<PermissionHandlerView> { class PermissionHandlerViewState extends State<PermissionHandlerView> {
Future<Map<Permission, PermissionStatus>> permissionServices() async { Future<Map<Permission, PermissionStatus>> permissionServices() async {
// try { // try {
Map<Permission, PermissionStatus> statuses = await [ final statuses = await [
Permission.camera, Permission.camera,
// Permission.microphone, // Permission.microphone,
Permission.notification Permission.notification
@ -57,18 +59,17 @@ class PermissionHandlerViewState extends State<PermissionHandlerView> {
return Scaffold( return Scaffold(
body: Center( body: Center(
child: Container( child: Container(
padding: EdgeInsets.all(100), padding: const EdgeInsets.all(100),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( const Text(
"twonly needs access to the camera and microphone.", 'twonly needs access to the camera and microphone.',
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 50), const SizedBox(height: 50),
FilledButton.icon( FilledButton.icon(
label: Text("Request permissions"), label: const Text('Request permissions'),
icon: const Icon(Icons.perm_camera_mic), icon: const Icon(Icons.perm_camera_mic),
onPressed: () async { onPressed: () async {
try { try {

View file

@ -1,31 +1,31 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:twonly/src/services/api/media_upload.dart'; import 'package:twonly/src/services/api/media_upload.dart';
import 'package:twonly/src/services/thumbnail.service.dart'; import 'package:twonly/src/services/thumbnail.service.dart';
import 'dart:typed_data';
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/utils/storage.dart';
class SaveToGalleryButton extends StatefulWidget { class SaveToGalleryButton extends StatefulWidget {
const SaveToGalleryButton({
required this.getMergedImage,
required this.isLoading,
required this.displayButtonLabel,
super.key,
this.mediaUploadId,
this.videoFilePath,
});
final Future<Uint8List?> Function() getMergedImage; final Future<Uint8List?> Function() getMergedImage;
final bool displayButtonLabel; final bool displayButtonLabel;
final File? videoFilePath; final File? videoFilePath;
final int? mediaUploadId; final int? mediaUploadId;
final bool isLoading; final bool isLoading;
const SaveToGalleryButton({
super.key,
required this.getMergedImage,
required this.isLoading,
required this.displayButtonLabel,
this.mediaUploadId,
this.videoFilePath,
});
@override @override
State<SaveToGalleryButton> createState() => SaveToGalleryButtonState(); State<SaveToGalleryButton> createState() => SaveToGalleryButtonState();
} }
@ -53,33 +53,33 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
}); });
String? res; String? res;
String memoryPath = await getMediaBaseFilePath("memories"); var memoryPath = await getMediaBaseFilePath('memories');
if (widget.mediaUploadId != null) { if (widget.mediaUploadId != null) {
memoryPath = join(memoryPath, "${widget.mediaUploadId!}"); memoryPath = join(memoryPath, '${widget.mediaUploadId!}');
} else { } else {
final Random random = Random(); final random = Random();
String token = uint8ListToHex( final token = uint8ListToHex(
List<int>.generate(32, (i) => random.nextInt(256))); List<int>.generate(32, (i) => random.nextInt(256)));
memoryPath = join(memoryPath, token); memoryPath = join(memoryPath, token);
} }
final user = await getUser(); final user = await getUser();
if (user == null) return; if (user == null) return;
bool storeToGallery = user.storeMediaFilesInGallery; final storeToGallery = user.storeMediaFilesInGallery;
if (widget.videoFilePath != null) { if (widget.videoFilePath != null) {
memoryPath += ".mp4"; memoryPath += '.mp4';
await File(widget.videoFilePath!.path).copy(memoryPath); await File(widget.videoFilePath!.path).copy(memoryPath);
createThumbnailsForVideo(File(memoryPath)); unawaited(createThumbnailsForVideo(File(memoryPath)));
if (storeToGallery) { if (storeToGallery) {
res = await saveVideoToGallery(widget.videoFilePath!.path); res = await saveVideoToGallery(widget.videoFilePath!.path);
} }
} else { } else {
memoryPath += ".png"; memoryPath += '.png';
Uint8List? imageBytes = await widget.getMergedImage(); final imageBytes = await widget.getMergedImage();
if (imageBytes == null || !mounted) return; if (imageBytes == null || !mounted) return;
await File(memoryPath).writeAsBytes(imageBytes); await File(memoryPath).writeAsBytes(imageBytes);
createThumbnailsForImage(File(memoryPath)); unawaited(createThumbnailsForImage(File(memoryPath)));
if (storeToGallery) { if (storeToGallery) {
res = await saveImageToGallery(imageBytes); res = await saveImageToGallery(imageBytes);
} }
@ -92,7 +92,7 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(res), content: Text(res),
duration: Duration(seconds: 3), duration: const Duration(seconds: 3),
), ),
); );
} }
@ -102,15 +102,16 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
}, },
child: Row( child: Row(
children: [ children: [
(_imageSaving || widget.isLoading) if (_imageSaving || widget.isLoading)
? SizedBox( const SizedBox(
width: 12, width: 12,
height: 12, height: 12,
child: CircularProgressIndicator(strokeWidth: 1)) child: CircularProgressIndicator(strokeWidth: 1))
: _imageSaved else
? Icon(Icons.check) _imageSaved
: FaIcon(FontAwesomeIcons.floppyDisk), ? const Icon(Icons.check)
if (widget.displayButtonLabel) SizedBox(width: 10), : const FaIcon(FontAwesomeIcons.floppyDisk),
if (widget.displayButtonLabel) const SizedBox(width: 10),
if (widget.displayButtonLabel) if (widget.displayButtonLabel)
Text(_imageSaved Text(_imageSaved
? context.lang.shareImagedEditorSavedImage ? context.lang.shareImagedEditorSavedImage

View file

@ -2,29 +2,28 @@ import 'package:flutter/material.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
class SendToWidget extends StatelessWidget { class SendToWidget extends StatelessWidget {
final String sendTo;
const SendToWidget({ const SendToWidget({
super.key,
required this.sendTo, required this.sendTo,
super.key,
}); });
final String sendTo;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
TextStyle textStyle = TextStyle( const textStyle = TextStyle(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 24, fontSize: 24,
decoration: TextDecoration.none, decoration: TextDecoration.none,
shadows: [ shadows: [
Shadow( Shadow(
color: const Color.fromARGB(122, 0, 0, 0), color: Color.fromARGB(122, 0, 0, 0),
blurRadius: 5.0, blurRadius: 5,
), ),
], ],
); );
TextStyle boldTextStyle = textStyle.copyWith( final boldTextStyle = textStyle.copyWith(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
fontSize: 28, fontSize: 28,
); );

View file

@ -1,14 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class VideoRecordingTimer extends StatelessWidget { class VideoRecordingTimer extends StatelessWidget {
final DateTime? videoRecordingStarted;
final int maxVideoRecordingTime;
const VideoRecordingTimer({ const VideoRecordingTimer({
super.key,
required this.videoRecordingStarted, required this.videoRecordingStarted,
required this.maxVideoRecordingTime, required this.maxVideoRecordingTime,
super.key,
}); });
final DateTime? videoRecordingStarted;
final int maxVideoRecordingTime;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -26,11 +25,12 @@ class VideoRecordingTimer extends StatelessWidget {
children: [ children: [
Center( Center(
child: CircularProgressIndicator( child: CircularProgressIndicator(
value: (currentTime.difference(videoRecordingStarted!)) value: currentTime
.difference(videoRecordingStarted!)
.inMilliseconds / .inMilliseconds /
(maxVideoRecordingTime * 1000), (maxVideoRecordingTime * 1000),
strokeWidth: 4, strokeWidth: 4,
valueColor: AlwaysStoppedAnimation<Color>(Colors.red), valueColor: const AlwaysStoppedAnimation<Color>(Colors.red),
backgroundColor: Colors.grey[300], backgroundColor: Colors.grey[300],
), ),
), ),
@ -46,7 +46,7 @@ class VideoRecordingTimer extends StatelessWidget {
shadows: [ shadows: [
Shadow( Shadow(
color: Color.fromARGB(122, 0, 0, 0), color: Color.fromARGB(122, 0, 0, 0),
blurRadius: 5.0, blurRadius: 5,
) )
], ],
), ),

View file

@ -1,14 +1,17 @@
// ignore_for_file: avoid_dynamic_calls
import 'dart:math'; import 'dart:math';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class CameraZoomButtons extends StatefulWidget { class CameraZoomButtons extends StatefulWidget {
const CameraZoomButtons( const CameraZoomButtons({
{super.key,
required this.controller, required this.controller,
required this.updateScaleFactor, required this.updateScaleFactor,
required this.scaleFactor}); required this.scaleFactor,
super.key,
});
final CameraController controller; final CameraController controller;
final double scaleFactor; final double scaleFactor;
@ -20,7 +23,7 @@ class CameraZoomButtons extends StatefulWidget {
String beautifulZoomScale(double scale) { String beautifulZoomScale(double scale) {
var tmp = scale.toStringAsFixed(1); var tmp = scale.toStringAsFixed(1);
if (tmp[0] == "0") { if (tmp[0] == '0') {
tmp = tmp.substring(1, tmp.length); tmp = tmp.substring(1, tmp.length);
} }
return tmp; return tmp;
@ -50,20 +53,20 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var zoomButtonStyle = TextButton.styleFrom( final zoomButtonStyle = TextButton.styleFrom(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
foregroundColor: Colors.white, foregroundColor: Colors.white,
minimumSize: Size(40, 40), minimumSize: const Size(40, 40),
alignment: Alignment.center, alignment: Alignment.center,
tapTargetSize: MaterialTapTargetSize.shrinkWrap, tapTargetSize: MaterialTapTargetSize.shrinkWrap,
); );
final zoomTextStyle = TextStyle(fontSize: 13); const zoomTextStyle = TextStyle(fontSize: 13);
final isMiddleFocused = widget.scaleFactor >= 1 && widget.scaleFactor < 2; final isMiddleFocused = widget.scaleFactor >= 1 && widget.scaleFactor < 2;
return Center( return Center(
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(40.0), borderRadius: BorderRadius.circular(40),
child: Container( child: ColoredBox(
color: const Color.fromARGB(90, 0, 0, 0), color: const Color.fromARGB(90, 0, 0, 0),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -76,25 +79,24 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
), ),
), ),
onPressed: () async { onPressed: () async {
var level = await widget.controller.getMinZoomLevel(); final level = await widget.controller.getMinZoomLevel();
widget.updateScaleFactor(level); widget.updateScaleFactor(level);
}, },
child: FutureBuilder( child: FutureBuilder(
future: widget.controller.getMinZoomLevel(), future: widget.controller.getMinZoomLevel(),
builder: (context, snap) { builder: (context, snap) {
if (snap.hasData) { if (snap.hasData) {
var minLevel = final minLevel = beautifulZoomScale(snap.data!);
beautifulZoomScale(snap.data!.toDouble()); final currentLevel =
var currentLevel =
beautifulZoomScale(widget.scaleFactor); beautifulZoomScale(widget.scaleFactor);
return Text( return Text(
widget.scaleFactor < 1 widget.scaleFactor < 1
? "${currentLevel}x" ? '${currentLevel}x'
: "${minLevel}x", : '${minLevel}x',
style: zoomTextStyle, style: zoomTextStyle,
); );
} else { } else {
return Text(""); return const Text('');
} }
}, },
), ),
@ -109,9 +111,9 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
widget.updateScaleFactor(1.0); widget.updateScaleFactor(1.0);
}, },
child: Text( child: Text(
(isMiddleFocused) isMiddleFocused
? "${beautifulZoomScale(widget.scaleFactor)}x" ? '${beautifulZoomScale(widget.scaleFactor)}x'
: "1.0x", : '1.0x',
style: zoomTextStyle, style: zoomTextStyle,
)), )),
TextButton( TextButton(
@ -121,7 +123,8 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
), ),
), ),
onPressed: () async { onPressed: () async {
var level = min(await widget.controller.getMaxZoomLevel(), 2) final level =
min(await widget.controller.getMaxZoomLevel(), 2)
.toDouble(); .toDouble();
widget.updateScaleFactor(level); widget.updateScaleFactor(level);
}, },
@ -129,15 +132,15 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
future: widget.controller.getMaxZoomLevel(), future: widget.controller.getMaxZoomLevel(),
builder: (context, snap) { builder: (context, snap) {
if (snap.hasData) { if (snap.hasData) {
var maxLevel = max( final maxLevel = max(
min((snap.data?.toInt())!, 2), min((snap.data?.toInt())!, 2),
widget.scaleFactor, widget.scaleFactor,
); );
return Text( return Text(
"${beautifulZoomScale(maxLevel.toDouble())}x", '${beautifulZoomScale(maxLevel.toDouble())}x',
style: zoomTextStyle); style: zoomTextStyle);
} else { } else {
return Text(""); return const Text('');
} }
}), }),
) )

View file

@ -29,23 +29,25 @@ Future<(SelectedCameraDetails, CameraController)?> initializeCameraController(
SelectedCameraDetails details, SelectedCameraDetails details,
int sCameraId, int sCameraId,
bool init, bool init,
bool enableAudio) async { bool enableAudio,
if (sCameraId >= gCameras.length) return null; ) async {
var cameraId = sCameraId;
if (cameraId >= gCameras.length) return null;
if (init) { if (init) {
for (; sCameraId < gCameras.length; sCameraId++) { for (; cameraId < gCameras.length; cameraId++) {
if (gCameras[sCameraId].lensDirection == CameraLensDirection.back) { if (gCameras[cameraId].lensDirection == CameraLensDirection.back) {
break; break;
} }
} }
} }
details.isZoomAble = false; details.isZoomAble = false;
if (details.cameraId != sCameraId) { if (details.cameraId != cameraId) {
// switch between front and back // switch between front and back
details.scaleFactor = 1; details.scaleFactor = 1;
} }
final cameraController = CameraController( final cameraController = CameraController(
gCameras[sCameraId], gCameras[cameraId],
ResolutionPreset.high, ResolutionPreset.high,
enableAudio: enableAudio, enableAudio: enableAudio,
); );
@ -64,9 +66,9 @@ Future<(SelectedCameraDetails, CameraController)?> initializeCameraController(
details details
..isZoomAble = details.maxAvailableZoom != details.minAvailableZoom ..isZoomAble = details.maxAvailableZoom != details.minAvailableZoom
..cameraLoaded = true ..cameraLoaded = true
..cameraId = sCameraId; ..cameraId = cameraId;
}).catchError((Object e) { }).catchError((Object e) {
Log.error("$e"); Log.error('$e');
}); });
return (details, cameraController); return (details, cameraController);
} }
@ -213,7 +215,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Future<Uint8List?> loadAndDeletePictureFromFile(XFile picture) async { Future<Uint8List?> loadAndDeletePictureFromFile(XFile picture) async {
try { try {
// Load the image into bytes // Load the image into bytes
final Uint8List imageBytes = await picture.readAsBytes(); final imageBytes = await picture.readAsBytes();
// Remove the image file // Remove the image file
await File(picture.path).delete(); await File(picture.path).delete();
return imageBytes; return imageBytes;
@ -400,7 +402,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
videoRecordingTimer = null; videoRecordingTimer = null;
} }
if (cameraController == null || !cameraController!.value.isRecordingVideo) { if (cameraController == null || !cameraController!.value.isRecordingVideo) {
return null; return;
} }
try { try {
@ -414,8 +416,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (videoPath != null) { if (videoPath != null) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
// see https://github.com/flutter/flutter/issues/148335 // see https://github.com/flutter/flutter/issues/148335
await File(videoPath.path).rename("${videoPath.path}.mp4"); await File(videoPath.path).rename('${videoPath.path}.mp4');
videoPathFile = File("${videoPath.path}.mp4"); videoPathFile = File('${videoPath.path}.mp4');
} else { } else {
videoPathFile = File(videoPath.path); videoPathFile = File(videoPath.path);
} }
@ -426,12 +428,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
} }
} on CameraException catch (e) { } on CameraException catch (e) {
_showCameraException(e); _showCameraException(e);
return null; return;
} }
} }
void _showCameraException(dynamic e) { void _showCameraException(dynamic e) {
Log.error("$e"); Log.error('$e');
try { try {
if (context.mounted) { if (context.mounted) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
@ -536,10 +538,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
: Colors.white.withAlpha(160), : Colors.white.withAlpha(160),
onPressed: () async { onPressed: () async {
if (selectedCameraDetails.isFlashOn) { if (selectedCameraDetails.isFlashOn) {
cameraController?.setFlashMode(FlashMode.off); await cameraController
?.setFlashMode(FlashMode.off);
selectedCameraDetails.isFlashOn = false; selectedCameraDetails.isFlashOn = false;
} else { } else {
cameraController?.setFlashMode(FlashMode.always); await cameraController
?.setFlashMode(FlashMode.always);
selectedCameraDetails.isFlashOn = true; selectedCameraDetails.isFlashOn = true;
} }
setState(() {}); setState(() {});
@ -566,16 +570,16 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
Icons.mic_off_rounded, Icons.mic_off_rounded,
color: Colors.white.withAlpha(160), color: Colors.white.withAlpha(160),
tooltipText: tooltipText:
"Allow microphone access for video recording.", 'Allow microphone access for video recording.',
onPressed: requestMicrophonePermission, onPressed: requestMicrophonePermission,
), ),
if (hasAudioPermission) if (hasAudioPermission)
ActionButton( ActionButton(
(videoWithAudio) videoWithAudio
? Icons.volume_up_rounded ? Icons.volume_up_rounded
: Icons.volume_off_rounded, : Icons.volume_off_rounded,
tooltipText: "Record video with audio.", tooltipText: 'Record video with audio.',
color: (videoWithAudio) color: videoWithAudio
? Colors.white ? Colors.white
: Colors.white.withAlpha(160), : Colors.white.withAlpha(160),
onPressed: () async { onPressed: () async {
@ -614,13 +618,11 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
const SizedBox(height: 30), const SizedBox(height: 30),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (!isVideoRecording) if (!isVideoRecording)
GestureDetector( GestureDetector(
onTap: pickImageFromGallery, onTap: pickImageFromGallery,
child: Align( child: Align(
alignment: Alignment.center,
child: Container( child: Container(
height: 50, height: 50,
width: 80, width: 80,
@ -640,7 +642,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
// onLongPress: startVideoRecording, // onLongPress: startVideoRecording,
key: keyTriggerButton, key: keyTriggerButton,
child: Align( child: Align(
alignment: Alignment.center,
child: Container( child: Container(
height: 100, height: 100,
width: 100, width: 100,

View file

@ -44,7 +44,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
Future<void> toggleSelectedCamera() async { Future<void> toggleSelectedCamera() async {
await cameraController?.dispose(); await cameraController?.dispose();
cameraController = null; cameraController = null;
selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false); await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
} }
@override @override
@ -54,7 +54,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
onDoubleTap: toggleSelectedCamera, onDoubleTap: toggleSelectedCamera,
child: Stack( child: Stack(
children: [ children: [
SendToCameraPreview(), const SendToCameraPreview(),
CameraPreviewControllerView( CameraPreviewControllerView(
selectCamera: selectCamera, selectCamera: selectCamera,
sendTo: widget.sendTo, sendTo: widget.sendTo,

View file

@ -2,19 +2,20 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class ActionButton extends StatelessWidget { class ActionButton extends StatelessWidget {
const ActionButton(
this.icon, {
required this.tooltipText,
super.key,
this.onPressed,
this.color,
this.disable = false,
});
final VoidCallback? onPressed; final VoidCallback? onPressed;
final IconData? icon; final IconData? icon;
final Color? color; final Color? color;
final String tooltipText; final String tooltipText;
final bool disable; final bool disable;
const ActionButton(this.icon,
{super.key,
this.onPressed,
this.color,
required this.tooltipText,
this.disable = false});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Tooltip( return Tooltip(
@ -26,10 +27,10 @@ class ActionButton extends StatelessWidget {
color: disable color: disable
? const Color.fromARGB(154, 255, 255, 255) ? const Color.fromARGB(154, 255, 255, 255)
: color ?? Colors.white, : color ?? Colors.white,
shadows: [ shadows: const [
Shadow( Shadow(
color: const Color.fromARGB(122, 0, 0, 0), color: Color.fromARGB(122, 0, 0, 0),
blurRadius: 5.0, blurRadius: 5,
) )
], ],
), ),

View file

@ -3,15 +3,14 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ImageItem { class ImageItem {
ImageItem([dynamic image]) {
if (image != null) load(image);
}
int width = 1; int width = 1;
int height = 1; int height = 1;
Uint8List bytes = Uint8List.fromList([]); Uint8List bytes = Uint8List.fromList([]);
Completer<bool> loader = Completer<bool>(); Completer<bool> loader = Completer<bool>();
ImageItem([dynamic image]) {
if (image != null) load(image);
}
Future<void> load(dynamic image) async { Future<void> load(dynamic image) async {
loader = Completer<bool>(); loader = Completer<bool>();
@ -24,7 +23,7 @@ class ImageItem {
return loader.complete(true); return loader.complete(true);
} else if (image is Uint8List) { } else if (image is Uint8List) {
bytes = image; bytes = image;
var decodedImage = await decodeImageFromList(bytes); final decodedImage = await decodeImageFromList(bytes);
height = decodedImage.height; height = decodedImage.height;
width = decodedImage.width; width = decodedImage.width;

View file

@ -1,18 +1,13 @@
// ignore_for_file: comment_references
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hand_signature/signature.dart'; import 'package:hand_signature/signature.dart';
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart'; import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
/// Layer class with some common properties /// Layer class with some common properties
class Layer { class Layer {
Offset offset;
double rotation, scale, opacity;
bool isEditing;
bool isDeleted;
bool hasCustomActionButtons;
bool showCustomButtons;
Layer({ Layer({
this.offset = const Offset(0, 0), this.offset = Offset.zero,
this.opacity = 1, this.opacity = 1,
this.isEditing = false, this.isEditing = false,
this.isDeleted = false, this.isDeleted = false,
@ -21,24 +16,28 @@ class Layer {
this.rotation = 0, this.rotation = 0,
this.scale = 1, this.scale = 1,
}); });
Offset offset;
double rotation;
double scale;
double opacity;
bool isEditing;
bool isDeleted;
bool hasCustomActionButtons;
bool showCustomButtons;
} }
/// Attributes used by [BackgroundLayer] /// Attributes used by [BackgroundLayer]
class BackgroundLayerData extends Layer { class BackgroundLayerData extends Layer {
ImageItem image;
BackgroundLayerData({ BackgroundLayerData({
required this.image, required this.image,
}); });
ImageItem image;
} }
class FilterLayerData extends Layer {} class FilterLayerData extends Layer {}
/// Attributes used by [EmojiLayer] /// Attributes used by [EmojiLayer]
class EmojiLayerData extends Layer { class EmojiLayerData extends Layer {
String text;
double size;
EmojiLayerData({ EmojiLayerData({
this.text = '', this.text = '',
this.size = 64, this.size = 64,
@ -48,31 +47,27 @@ class EmojiLayerData extends Layer {
super.scale, super.scale,
super.isEditing, super.isEditing,
}); });
String text;
double size;
} }
/// Attributes used by [TextLayer] /// Attributes used by [TextLayer]
class TextLayerData extends Layer { class TextLayerData extends Layer {
String text;
int textLayersBefore;
TextLayerData({ TextLayerData({
this.text = "",
required this.textLayersBefore, required this.textLayersBefore,
this.text = '',
super.offset, super.offset,
super.opacity, super.opacity,
super.rotation, super.rotation,
super.scale, super.scale,
super.isEditing = true, super.isEditing = true,
}); });
String text;
int textLayersBefore;
} }
/// Attributes used by [DrawLayer] /// Attributes used by [DrawLayer]
class DrawLayerData extends Layer { class DrawLayerData extends Layer {
final control = HandSignatureControl(
threshold: 3.0,
smoothRatio: 0.65,
velocityRange: 2.0,
);
// String text; // String text;
DrawLayerData({ DrawLayerData({
super.offset, super.offset,
@ -82,4 +77,5 @@ class DrawLayerData extends Layer {
super.hasCustomActionButtons = true, super.hasCustomActionButtons = true,
super.isEditing = true, super.isEditing = true,
}); });
final control = HandSignatureControl();
} }

View file

@ -3,14 +3,13 @@ import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
/// Main layer /// Main layer
class BackgroundLayer extends StatefulWidget { class BackgroundLayer extends StatefulWidget {
final BackgroundLayerData layerData;
final VoidCallback? onUpdate;
const BackgroundLayer({ const BackgroundLayer({
super.key,
required this.layerData, required this.layerData,
super.key,
this.onUpdate, this.onUpdate,
}); });
final BackgroundLayerData layerData;
final VoidCallback? onUpdate;
@override @override
State<BackgroundLayer> createState() => _BackgroundLayerState(); State<BackgroundLayer> createState() => _BackgroundLayerState();

View file

@ -2,27 +2,26 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hand_signature/signature.dart'; import 'package:hand_signature/signature.dart';
import 'package:screenshot/screenshot.dart'; import 'package:screenshot/screenshot.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart'; import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart'; import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/utils/misc.dart';
class DrawLayer extends StatefulWidget { class DrawLayer extends StatefulWidget {
final DrawLayerData layerData;
final VoidCallback? onUpdate;
const DrawLayer({ const DrawLayer({
super.key,
required this.layerData, required this.layerData,
super.key,
this.onUpdate, this.onUpdate,
}); });
final DrawLayerData layerData;
final VoidCallback? onUpdate;
@override @override
createState() => _DrawLayerState(); State<DrawLayer> createState() => _DrawLayerState();
} }
class _DrawLayerState extends State<DrawLayer> { class _DrawLayerState extends State<DrawLayer> {
Color currentColor = Colors.red; Color currentColor = Colors.red;
var screenshotController = ScreenshotController(); ScreenshotController screenshotController = ScreenshotController();
List<CubicPath> undoList = []; List<CubicPath> undoList = [];
bool skipNextEvent = false; bool skipNextEvent = false;
@ -48,7 +47,7 @@ class _DrawLayerState extends State<DrawLayer> {
double _sliderValue = 0.125; double _sliderValue = 0.125;
final colors = [ final List<Color> colors = [
Colors.white, Colors.white,
Colors.red, Colors.red,
Colors.orange, Colors.orange,
@ -61,11 +60,11 @@ class _DrawLayerState extends State<DrawLayer> {
Color _getColorFromSliderValue(double value) { Color _getColorFromSliderValue(double value) {
// Calculate the index based on the slider value // Calculate the index based on the slider value
int index = (value * (colors.length - 1)).floor(); final index = (value * (colors.length - 1)).floor();
int nextIndex = (index + 1).clamp(0, colors.length - 1); final nextIndex = (index + 1).clamp(0, colors.length - 1);
// Calculate the interpolation factor // Calculate the interpolation factor
double factor = value * (colors.length - 1) - index; final factor = value * (colors.length - 1) - index;
// Interpolate between the two colors // Interpolate between the two colors
return Color.lerp(colors[index], colors[nextIndex], factor)!; return Color.lerp(colors[index], colors[nextIndex], factor)!;
@ -85,17 +84,14 @@ class _DrawLayerState extends State<DrawLayer> {
children: [ children: [
Positioned.fill( Positioned.fill(
child: Container( child: Container(
decoration: BoxDecoration( decoration: const BoxDecoration(
color: Colors.transparent, color: Colors.transparent,
), ),
child: Screenshot( child: Screenshot(
controller: screenshotController, controller: screenshotController,
child: HandSignature( child: HandSignature(
control: widget.layerData.control, control: widget.layerData.control,
color: currentColor, drawer: LineSignatureDrawer(color: currentColor, width: 7),
width: 1.0,
maxWidth: 7.0,
type: SignatureDrawType.shape,
), ),
), ),
), ),
@ -112,9 +108,7 @@ class _DrawLayerState extends State<DrawLayer> {
tooltipText: context.lang.imageEditorDrawOk, tooltipText: context.lang.imageEditorDrawOk,
onPressed: () async { onPressed: () async {
widget.layerData.isEditing = false; widget.layerData.isEditing = false;
if (widget.onUpdate != null) {
widget.onUpdate!(); widget.onUpdate!();
}
setState(() {}); setState(() {});
}, },
), ),
@ -200,8 +194,6 @@ class _DrawLayerState extends State<DrawLayer> {
showMagnifyingGlass = false; showMagnifyingGlass = false;
}) })
}, },
min: 0.0,
max: 1.0,
divisions: 100, divisions: 100,
), ),
), ),
@ -226,10 +218,9 @@ class _DrawLayerState extends State<DrawLayer> {
} }
class MagnifyingGlass extends StatelessWidget { class MagnifyingGlass extends StatelessWidget {
const MagnifyingGlass({required this.color, super.key});
final Color color; final Color color;
const MagnifyingGlass({super.key, required this.color});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(

View file

@ -68,10 +68,8 @@ class _EmojiLayerState extends State<EmojiLayer> {
} }
if (deleteLayer) { if (deleteLayer) {
widget.layerData.isDeleted = true; widget.layerData.isDeleted = true;
if (widget.onUpdate != null) {
widget.onUpdate!(); widget.onUpdate!();
} }
}
}); });
}, },
onPointerDown: (details) { onPointerDown: (details) {
@ -138,7 +136,7 @@ class _EmojiLayerState extends State<EmojiLayer> {
padding: const EdgeInsets.all(44), padding: const EdgeInsets.all(44),
color: Colors.transparent, color: Colors.transparent,
child: Text( child: Text(
widget.layerData.text.toString(), widget.layerData.text,
style: TextStyle( style: TextStyle(
fontSize: widget.layerData.size, fontSize: widget.layerData.size,
), ),
@ -157,7 +155,7 @@ class _EmojiLayerState extends State<EmojiLayer> {
child: GestureDetector( child: GestureDetector(
child: ActionButton( child: ActionButton(
FontAwesomeIcons.trashCan, FontAwesomeIcons.trashCan,
tooltipText: "", tooltipText: '',
color: deleteLayer ? Colors.red : Colors.white, color: deleteLayer ? Colors.red : Colors.white,
), ),
), ),

View file

@ -60,7 +60,7 @@ class FilterText extends StatelessWidget {
shadows: const [ shadows: const [
Shadow( Shadow(
color: Color.fromARGB(122, 0, 0, 0), color: Color.fromARGB(122, 0, 0, 0),
blurRadius: 5.0, blurRadius: 5,
) )
], ],
), ),

View file

@ -9,8 +9,8 @@ class DateTimeFilter extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String currentTime = DateFormat('HH:mm').format(DateTime.now()); final currentTime = DateFormat('HH:mm').format(DateTime.now());
String currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now()); final currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now());
return FilterSkeleton( return FilterSkeleton(
child: Positioned( child: Positioned(
bottom: 80, bottom: 80,

View file

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart'; import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
class ImageFilter extends StatelessWidget { class ImageFilter extends StatelessWidget {
const ImageFilter({super.key, required this.imagePath}); const ImageFilter({required this.imagePath, super.key});
final String imagePath; final String imagePath;
@override @override
@ -15,7 +15,7 @@ class ImageFilter extends StatelessWidget {
right: 10, right: 10,
child: Center( child: Center(
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: "https://twonly.eu/$imagePath", imageUrl: 'https://twonly.eu/$imagePath',
height: 150, height: 150,
), ),
), ),

View file

@ -99,7 +99,6 @@ class _LocationFilterState extends State<LocationFilter> {
bottom: 50, bottom: 50,
left: 40, left: 40,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
FilterText(location!.city), FilterText(location!.city),
FilterText(location!.county), FilterText(location!.county),
@ -131,8 +130,8 @@ Future<List<Sticker>> getStickerIndex() async {
final indexFile = File('${directory.path}/stickers.json'); final indexFile = File('${directory.path}/stickers.json');
var res = <Sticker>[]; var res = <Sticker>[];
if (await indexFile.exists() && !kDebugMode) { if (indexFile.existsSync() && !kDebugMode) {
final lastModified = await indexFile.lastModified(); final lastModified = indexFile.lastModifiedSync();
final difference = DateTime.now().difference(lastModified); final difference = DateTime.now().difference(lastModified);
final content = await indexFile.readAsString(); final content = await indexFile.readAsString();
final jsonList = json.decode(content) as List; final jsonList = json.decode(content) as List;

View file

@ -1,3 +1,5 @@
// ignore_for_file: prefer_null_aware_method_calls
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -8,16 +10,15 @@ import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
/// Text layer /// Text layer
class TextLayer extends StatefulWidget { class TextLayer extends StatefulWidget {
final TextLayerData layerData;
final VoidCallback? onUpdate;
const TextLayer({ const TextLayer({
super.key,
required this.layerData, required this.layerData,
super.key,
this.onUpdate, this.onUpdate,
}); });
final TextLayerData layerData;
final VoidCallback? onUpdate;
@override @override
createState() => _TextViewState(); State<TextLayer> createState() => _TextViewState();
} }
class _TextViewState extends State<TextLayer> { class _TextViewState extends State<TextLayer> {
@ -69,7 +70,7 @@ class _TextViewState extends State<TextLayer> {
minLines: 1, minLines: 1,
onEditingComplete: () { onEditingComplete: () {
setState(() { setState(() {
widget.layerData.isDeleted = textController.text == ""; widget.layerData.isDeleted = textController.text == '';
widget.layerData.isEditing = false; widget.layerData.isEditing = false;
widget.layerData.text = textController.text; widget.layerData.text = textController.text;
}); });
@ -83,10 +84,10 @@ class _TextViewState extends State<TextLayer> {
}, },
onTapOutside: (a) { onTapOutside: (a) {
widget.layerData.text = textController.text; widget.layerData.text = textController.text;
Future.delayed(Duration(milliseconds: 100), () { Future.delayed(const Duration(milliseconds: 100), () {
if (context.mounted) { if (context.mounted) {
setState(() { setState(() {
widget.layerData.isDeleted = textController.text == ""; widget.layerData.isDeleted = textController.text == '';
widget.layerData.isEditing = false; widget.layerData.isEditing = false;
context context
.read<ImageEditorProvider>() .read<ImageEditorProvider>()
@ -102,10 +103,10 @@ class _TextViewState extends State<TextLayer> {
.read<ImageEditorProvider>() .read<ImageEditorProvider>()
.updateSomeTextViewIsAlreadyEditing(false); .updateSomeTextViewIsAlreadyEditing(false);
}, },
decoration: InputDecoration( decoration: const InputDecoration(
border: InputBorder.none, border: InputBorder.none,
isDense: true, isDense: true,
contentPadding: const EdgeInsets.symmetric( contentPadding: EdgeInsets.symmetric(
horizontal: 8, horizontal: 8,
vertical: 4, vertical: 4,
), ),
@ -136,7 +137,7 @@ class _TextViewState extends State<TextLayer> {
onScaleEnd: (d) { onScaleEnd: (d) {
if (deleteLayer) { if (deleteLayer) {
widget.layerData.isDeleted = true; widget.layerData.isDeleted = true;
textController.text = ""; textController.text = '';
} }
elementIsScaled = false; elementIsScaled = false;
if (widget.onUpdate != null) { if (widget.onUpdate != null) {
@ -161,8 +162,8 @@ class _TextViewState extends State<TextLayer> {
widget.layerData.offset = Offset( widget.layerData.offset = Offset(
0, widget.layerData.offset.dy + detail.focalPointDelta.dy); 0, widget.layerData.offset.dy + detail.focalPointDelta.dy);
} }
final RenderBox renderBox = final renderBox =
_widgetKey.currentContext!.findRenderObject() as RenderBox; _widgetKey.currentContext!.findRenderObject()! as RenderBox;
if (widget.layerData.offset.dy > renderBox.size.height - 80) { if (widget.layerData.offset.dy > renderBox.size.height - 80) {
if (!deleteLayer) { if (!deleteLayer) {
@ -198,11 +199,11 @@ class _TextViewState extends State<TextLayer> {
child: Center( child: Center(
child: GestureDetector( child: GestureDetector(
onTapUp: (d) { onTapUp: (d) {
textController.text = ""; textController.text = '';
}, },
child: ActionButton( child: ActionButton(
FontAwesomeIcons.trashCan, FontAwesomeIcons.trashCan,
tooltipText: "", tooltipText: '',
color: deleteLayer ? Colors.red : Colors.white, color: deleteLayer ? Colors.red : Colors.white,
), ),
), ),

View file

@ -7,7 +7,7 @@ class Emojis extends StatefulWidget {
const Emojis({super.key}); const Emojis({super.key});
@override @override
createState() => _EmojisState(); State<Emojis> createState() => _EmojisState();
} }
class _EmojisState extends State<Emojis> { class _EmojisState extends State<Emojis> {
@ -57,7 +57,7 @@ class _EmojisState extends State<Emojis> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SingleChildScrollView( return SingleChildScrollView(
child: Container( child: Container(
padding: const EdgeInsets.all(0.0), padding: EdgeInsets.zero,
height: 400, height: 400,
decoration: const BoxDecoration( decoration: const BoxDecoration(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@ -77,14 +77,12 @@ class _EmojisState extends State<Emojis> {
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
height: 315, height: 315,
padding: const EdgeInsets.all(0.0), padding: EdgeInsets.zero,
child: GridView( child: GridView(
shrinkWrap: true, shrinkWrap: true,
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
scrollDirection: Axis.vertical,
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisSpacing: 0.0, maxCrossAxisExtent: 60,
maxCrossAxisExtent: 60.0,
), ),
children: lastUsed.map((String emoji) { children: lastUsed.map((String emoji) {
return GridTile( return GridTile(

View file

@ -58,7 +58,7 @@ class BestFriendsSelector extends StatelessWidget {
color: Color.fromRGBO(0, 0, 0, 0.1), color: Color.fromRGBO(0, 0, 0, 0.1),
), ),
], ],
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8),
), ),
child: Text(context.lang.shareImagedSelectAll, child: Text(context.lang.shareImagedSelectAll,
style: const TextStyle(fontSize: 10)), style: const TextStyle(fontSize: 10)),
@ -143,7 +143,7 @@ class UserCheckbox extends StatelessWidget {
color: Color.fromRGBO(0, 0, 0, 0.1), color: Color.fromRGBO(0, 0, 0, 0.1),
), ),
], ],
borderRadius: BorderRadius.circular(8.0), borderRadius: BorderRadius.circular(8),
), ),
child: Row( child: Row(
children: [ children: [

View file

@ -61,7 +61,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
bool loadingImage = true; bool loadingImage = true;
bool isDisposed = false; bool isDisposed = false;
HashSet<int> selectedUserIds = HashSet(); HashSet<int> selectedUserIds = HashSet();
double widthRatio = 1, heightRatio = 1, pixelRatio = 1; double widthRatio = 1;
double heightRatio = 1;
double pixelRatio = 1;
VideoPlayerController? videoController; VideoPlayerController? videoController;
ImageItem currentImage = ImageItem(); ImageItem currentImage = ImageItem();
ScreenshotController screenshotController = ScreenshotController(); ScreenshotController screenshotController = ScreenshotController();
@ -93,7 +95,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
} }
} }
void initAsync() async { Future<void> initAsync() async {
final user = await getUser(); final user = await getUser();
if (user == null) return; if (user == null) return;
if (user.defaultShowTime != null) { if (user.defaultShowTime != null) {

View file

@ -56,7 +56,7 @@ class _ShareImageView extends State<ShareImageView> {
final TextEditingController searchUserName = TextEditingController(); final TextEditingController searchUserName = TextEditingController();
bool showRealTwonlyWarning = false; bool showRealTwonlyWarning = false;
late StreamSubscription<List<Contact>> contactSub; late StreamSubscription<List<Contact>> contactSub;
String lastQuery = ""; String lastQuery = '';
@override @override
void initState() { void initState() {

View file

@ -39,9 +39,9 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
return Card( return Card(
elevation: 4, elevation: 4,
margin: EdgeInsets.all(10), margin: const EdgeInsets.all(10),
child: Padding( child: Padding(
padding: const EdgeInsets.all(10.0), padding: const EdgeInsets.all(10),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -52,7 +52,7 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
SizedBox(height: 5), const SizedBox(height: 5),
Text( Text(
context.lang.backupNoticeDesc, context.lang.backupNoticeDesc,
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
@ -64,20 +64,20 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
onPressed: () async { onPressed: () async {
await updateUserdata((user) { await updateUserdata((user) {
user.nextTimeToShowBackupNotice = user.nextTimeToShowBackupNotice =
DateTime.now().add(Duration(days: 7)); DateTime.now().add(const Duration(days: 7));
return user; return user;
}); });
initAsync(); await initAsync();
}, },
child: Text(context.lang.backupNoticeLater), child: Text(context.lang.backupNoticeLater),
), ),
SizedBox(width: 10), const SizedBox(width: 10),
FilledButton( FilledButton(
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => BackupView(), builder: (context) => const BackupView(),
), ),
); );
}, },

View file

@ -62,7 +62,7 @@ class _ConnectionInfoWidgetState extends State<ConnectionInfo>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!showAnimation || gIsDemoUser) return Container(); if (!showAnimation || gIsDemoUser) return Container();
double screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return SizedBox( return SizedBox(
width: screenWidth, width: screenWidth,
@ -70,8 +70,8 @@ class _ConnectionInfoWidgetState extends State<ConnectionInfo>
child: AnimatedBuilder( child: AnimatedBuilder(
animation: _controller, animation: _controller,
builder: (context, child) { builder: (context, child) {
double barWidth = _widthAnim.value; final barWidth = _widthAnim.value;
double left = _positionAnim.value * (screenWidth - barWidth); final left = _positionAnim.value * (screenWidth - barWidth);
return Stack( return Stack(
children: [ children: [
Positioned( Positioned(

View file

@ -8,11 +8,10 @@ class DemoUserCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return ColoredBox(
color: isDarkMode(context) ? Colors.white : Colors.black, color: isDarkMode(context) ? Colors.white : Colors.black,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Text(
'This is a Demo-Preview.', 'This is a Demo-Preview.',

View file

@ -157,7 +157,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
); );
} }
twonlyDB.messagesDao.openedAllNonMediaMessages(widget.contact.userId); await twonlyDB.messagesDao
.openedAllNonMediaMessages(widget.contact.userId);
setState(() { setState(() {
textReactionsToMessageId = tmpTextReactionsToMessageId; textReactionsToMessageId = tmpTextReactionsToMessageId;
@ -271,7 +272,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Expanded( Expanded(
child: Container( child: ColoredBox(
color: Colors.transparent, color: Colors.transparent,
child: Row( child: Row(
children: [ children: [

View file

@ -1,25 +1,24 @@
import 'package:drift/drift.dart' show Value; import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart';
import 'package:twonly/src/database/twonly_database.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/model/json/message.dart'; import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/services/api/media_download.dart' as received;
import 'package:twonly/src/views/chats/media_viewer.view.dart';
import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
import 'package:twonly/src/services/api/media_download.dart' as received;
import 'package:twonly/src/services/api/messages.dart';
import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart';
import 'package:twonly/src/views/chats/media_viewer.view.dart';
import 'package:twonly/src/views/tutorial/tutorials.dart'; import 'package:twonly/src/views/tutorial/tutorials.dart';
class ChatMediaEntry extends StatefulWidget { class ChatMediaEntry extends StatefulWidget {
const ChatMediaEntry({ const ChatMediaEntry({
super.key,
required this.message, required this.message,
required this.contact, required this.contact,
required this.content, required this.content,
required this.galleryItems, required this.galleryItems,
super.key,
}); });
final Message message; final Message message;
@ -47,13 +46,13 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
widget.message.mediaStored) { widget.message.mediaStored) {
return; return;
} }
if (await received.existsMediaFile(widget.message.messageId, "png")) { if (await received.existsMediaFile(widget.message.messageId, 'png')) {
if (mounted) { if (mounted) {
setState(() { setState(() {
canBeReopened = true; canBeReopened = true;
}); });
} }
Future.delayed(Duration(seconds: 1), () { Future.delayed(const Duration(seconds: 1), () {
if (!mounted) return; if (!mounted) return;
showReopenMediaFilesTutorial(context, reopenMediaFile); showReopenMediaFilesTutorial(context, reopenMediaFile);
}); });
@ -62,7 +61,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color color = getMessageColorFromType( final color = getMessageColorFromType(
widget.content, widget.content,
context, context,
); );
@ -75,7 +74,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
widget.message.mediaStored) { widget.message.mediaStored) {
return; return;
} }
if (await received.existsMediaFile(widget.message.messageId, "png")) { if (await received.existsMediaFile(widget.message.messageId, 'png')) {
await encryptAndSendMessageAsync( await encryptAndSendMessageAsync(
null, null,
widget.contact.userId, widget.contact.userId,
@ -93,7 +92,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
); );
await twonlyDB.messagesDao.updateMessageByMessageId( await twonlyDB.messagesDao.updateMessageByMessageId(
widget.message.messageId, widget.message.messageId,
MessagesCompanion(openedAt: Value(null)), const MessagesCompanion(openedAt: Value(null)),
); );
} }
}, },
@ -108,9 +107,9 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
initialMessage: widget.message); initialMessage: widget.message);
}), }),
); );
checkIfTutorialCanBeShown(); await checkIfTutorialCanBeShown();
} else if (widget.message.downloadState == DownloadState.pending) { } else if (widget.message.downloadState == DownloadState.pending) {
received.startDownloadMedia(widget.message, true); await received.startDownloadMedia(widget.message, true);
} }
} }
}, },

View file

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:twonly/src/database/twonly_database.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/components/animate_icon.dart'; import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/better_text.dart'; import 'package:twonly/src/views/components/better_text.dart';
import 'package:twonly/src/database/twonly_database.dart';
class ChatTextEntry extends StatelessWidget { class ChatTextEntry extends StatelessWidget {
const ChatTextEntry({super.key, required this.message, required this.text}); const ChatTextEntry({required this.message, required this.text, super.key});
final String text; final String text;
final Message message; final Message message;
@ -14,10 +14,10 @@ class ChatTextEntry extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (EmojiAnimation.supported(text)) { if (EmojiAnimation.supported(text)) {
return Container( return Container(
constraints: BoxConstraints( constraints: const BoxConstraints(
maxWidth: 100, maxWidth: 100,
), ),
padding: EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 4, vertical: 4,
horizontal: 10, horizontal: 10,
), ),
@ -28,10 +28,10 @@ class ChatTextEntry extends StatelessWidget {
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.8, maxWidth: MediaQuery.of(context).size.width * 0.8,
), ),
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 10), padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: getMessageColor(message), color: getMessageColor(message),
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12),
), ),
child: BetterText(text: text), child: BetterText(text: text),
); );

View file

@ -49,8 +49,7 @@ class ChatTextResponseColumns extends StatelessWidget {
children.insert( children.insert(
0, 0,
Container( Container(
padding: padding: const EdgeInsets.only(top: 5, right: 10, left: 10),
const EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10),
child: Container( child: Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.8, maxWidth: MediaQuery.of(context).size.width * 0.8,

View file

@ -34,18 +34,18 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
backupServer, backupServer,
); );
Restart.restartApp( await Restart.restartApp(
notificationTitle: 'Backup successfully recovered.', notificationTitle: 'Backup successfully recovered.',
notificationBody: 'Click here to open the app again', notificationBody: 'Click here to open the app again',
); );
} catch (e) { } catch (e) {
// in case something was already written from the backup... // in case something was already written from the backup...
Log.error("$e"); Log.error('$e');
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('$e'), content: Text('$e'),
duration: Duration(seconds: 3), duration: const Duration(seconds: 3),
), ),
); );
} }
@ -60,23 +60,24 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("twonly Safe ${context.lang.twonlySafeRecoverTitle}"), title: Text('twonly Safe ${context.lang.twonlySafeRecoverTitle}'),
actions: [ actions: [
IconButton( IconButton(
onPressed: () { onPressed: () {
showAlertDialog( showAlertDialog(
context, context,
"twonly Safe", 'twonly Safe',
context.lang.backupTwonlySafeLongDesc, context.lang.backupTwonlySafeLongDesc,
); );
}, },
icon: FaIcon(FontAwesomeIcons.circleInfo), icon: const FaIcon(FontAwesomeIcons.circleInfo),
iconSize: 18, iconSize: 18,
) )
], ],
), ),
body: Padding( body: Padding(
padding: EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40), padding:
const EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40),
child: ListView( child: ListView(
children: [ children: [
Text( Text(
@ -95,7 +96,7 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
context.lang.registerUsernameDecoration, context.lang.registerUsernameDecoration,
), ),
), ),
SizedBox(height: 10), const SizedBox(height: 10),
Stack( Stack(
children: [ children: [
TextField( TextField(
@ -130,30 +131,30 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
) )
], ],
), ),
SizedBox(height: 30), const SizedBox(height: 30),
Center( Center(
child: OutlinedButton( child: OutlinedButton(
onPressed: () async { onPressed: () async {
backupServer = await Navigator.push(context, backupServer = await Navigator.push(context,
MaterialPageRoute(builder: (context) { MaterialPageRoute(builder: (context) {
return TwonlySafeServerView(); return const TwonlySafeServerView();
})); }));
setState(() {}); setState(() {});
}, },
child: Text(context.lang.backupExpertSettings), child: Text(context.lang.backupExpertSettings),
), ),
), ),
SizedBox(height: 10), const SizedBox(height: 10),
Center( Center(
child: FilledButton.icon( child: FilledButton.icon(
onPressed: (!isLoading) ? _recoverTwonlySafe : null, onPressed: (!isLoading) ? _recoverTwonlySafe : null,
icon: isLoading icon: isLoading
? SizedBox( ? const SizedBox(
height: 12, height: 12,
width: 12, width: 12,
child: CircularProgressIndicator(strokeWidth: 1), child: CircularProgressIndicator(strokeWidth: 1),
) )
: Icon(Icons.lock_clock_rounded), : const Icon(Icons.lock_clock_rounded),
label: Text(context.lang.twonlySafeRecoverBtn), label: Text(context.lang.twonlySafeRecoverBtn),
)) ))
], ],

View file

@ -4,7 +4,7 @@ import 'package:twonly/src/views/settings/subscription/voucher.view.dart';
// import 'package:url_launcher/url_launcher.dart'; // import 'package:url_launcher/url_launcher.dart';
class RefundCreditsView extends StatefulWidget { class RefundCreditsView extends StatefulWidget {
const RefundCreditsView({super.key, required this.formattedBalance}); const RefundCreditsView({required this.formattedBalance, super.key});
final String formattedBalance; final String formattedBalance;
@override @override
@ -19,7 +19,7 @@ class _RefundCreditsViewState extends State<RefundCreditsView> {
title: const Text('Refund Credits'), title: const Text('Refund Credits'),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -32,13 +32,15 @@ class _RefundCreditsViewState extends State<RefundCreditsView> {
const SizedBox(height: 20), // Space between balance and options const SizedBox(height: 20), // Space between balance and options
ListTile( ListTile(
title: Text("Create a Voucher"), title: const Text('Create a Voucher'),
onTap: () async { onTap: () async {
await Navigator.push(context, await Navigator.push(context,
MaterialPageRoute(builder: (context) { MaterialPageRoute(builder: (context) {
return VoucherView(); return const VoucherView();
})); }));
if (context.mounted) {
Navigator.pop(context, false); Navigator.pop(context, false);
}
}, },
), ),
// ListTile( // ListTile(

View file

@ -38,7 +38,7 @@ class _ContactUsState extends State<ContactUsView> {
final messageOnSuccess = TextMessage() final messageOnSuccess = TextMessage()
..body = [] ..body = []
..userId = Int64(0); ..userId = Int64();
final uploadRequest = UploadRequest( final uploadRequest = UploadRequest(
messagesOnSuccess: [messageOnSuccess], messagesOnSuccess: [messageOnSuccess],
@ -116,6 +116,7 @@ class _ContactUsState extends State<ContactUsView> {
'Debug Log: https://api.twonly.eu/api/download/$token'; 'Debug Log: https://api.twonly.eu/api/download/$token';
} }
} catch (e) { } catch (e) {
if (!mounted) return '';
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Could not upload the debug log!')), const SnackBar(content: Text('Could not upload the debug log!')),
); );

View file

@ -3,7 +3,7 @@ import 'package:http/http.dart' as http;
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
class SubmitMessage extends StatefulWidget { class SubmitMessage extends StatefulWidget {
const SubmitMessage({super.key, required this.fullMessage}); const SubmitMessage({required this.fullMessage, super.key});
final String fullMessage; final String fullMessage;
@ -22,14 +22,14 @@ class _ContactUsState extends State<SubmitMessage> {
} }
Future<void> _submitFeedback() async { Future<void> _submitFeedback() async {
final String feedback = _controller.text; final feedback = _controller.text;
setState(() { setState(() {
isLoading = true; isLoading = true;
}); });
if (feedback.isEmpty) { if (feedback.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please enter your message.')), const SnackBar(content: Text('Please enter your message.')),
); );
return; return;
} }
@ -57,7 +57,7 @@ class _ContactUsState extends State<SubmitMessage> {
} else { } else {
// Handle error response // Handle error response
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to submit feedback.')), const SnackBar(content: Text('Failed to submit feedback.')),
); );
} }
} }
@ -69,30 +69,30 @@ class _ContactUsState extends State<SubmitMessage> {
title: Text(context.lang.settingsHelpContactUs), title: Text(context.lang.settingsHelpContactUs),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: ListView( child: ListView(
children: [ children: [
Text( Text(
context.lang.contactUsLastWarning, context.lang.contactUsLastWarning,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 10), const SizedBox(height: 10),
TextField( TextField(
controller: _controller, controller: _controller,
decoration: InputDecoration( decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage, hintText: context.lang.contactUsYourMessage,
border: OutlineInputBorder(), border: const OutlineInputBorder(),
), ),
minLines: 5, minLines: 5,
maxLines: 20, maxLines: 20,
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 40), padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: (isLoading) ? null : _submitFeedback, onPressed: isLoading ? null : _submitFeedback,
child: Text(context.lang.submit), child: Text(context.lang.submit),
), ),
], ],

View file

@ -6,19 +6,18 @@ import 'package:twonly/src/views/camera/image_editor/layers/filters/location_fil
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class UrlListTitle extends StatelessWidget { class UrlListTitle extends StatelessWidget {
const UrlListTitle({
required this.title,
required this.url,
super.key,
this.leading,
this.subtitle,
});
final String? title; final String? title;
final String url; final String url;
final String? subtitle; final String? subtitle;
final Widget? leading; final Widget? leading;
const UrlListTitle({
super.key,
required this.title,
required this.url,
this.leading,
this.subtitle,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
@ -28,7 +27,7 @@ class UrlListTitle extends StatelessWidget {
onTap: () { onTap: () {
launchUrl(Uri.parse(url)); launchUrl(Uri.parse(url));
}, },
trailing: FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15), trailing: const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15),
); );
} }
} }
@ -50,7 +49,7 @@ class _CreditsViewState extends State<CreditsView> {
} }
Future<void> initAsync() async { Future<void> initAsync() async {
sticker = (await getStickerIndex()).where((x) => x.source != "").toList(); sticker = (await getStickerIndex()).where((x) => x.source != '').toList();
setState(() {}); setState(() {});
} }
@ -62,86 +61,86 @@ class _CreditsViewState extends State<CreditsView> {
), ),
body: ListView( body: ListView(
children: [ children: [
UrlListTitle( const UrlListTitle(
title: "twonly Logo", title: 'twonly Logo',
subtitle: "by Font Awesome (modified)", subtitle: 'by Font Awesome (modified)',
url: "https://fontawesome.com/icons/link?f=classic&s=solid", url: 'https://fontawesome.com/icons/link?f=classic&s=solid',
), ),
UrlListTitle( const UrlListTitle(
title: "Most Icons", title: 'Most Icons',
subtitle: "by Font Awesome", subtitle: 'by Font Awesome',
url: "https://github.com/FortAwesome/Font-Awesome", url: 'https://github.com/FortAwesome/Font-Awesome',
), ),
UrlListTitle( const UrlListTitle(
title: "Animated Emoji", title: 'Animated Emoji',
subtitle: "CC BY 4.0", subtitle: 'CC BY 4.0',
url: "https://googlefonts.github.io/noto-emoji-animation/", url: 'https://googlefonts.github.io/noto-emoji-animation/',
), ),
UrlListTitle( const UrlListTitle(
title: "Avatar Icons", title: 'Avatar Icons',
url: "https://github.com/RoadTripMoustache/avatar_maker", url: 'https://github.com/RoadTripMoustache/avatar_maker',
), ),
const Divider(), const Divider(),
ListTile( const ListTile(
title: Center( title: Center(
child: Text( child: Text(
"Animations", 'Animations',
style: const TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
)), )),
), ),
UrlListTitle( const UrlListTitle(
title: "selfie fast Animation", title: 'selfie fast Animation',
subtitle: "Brandon Ambuila", subtitle: 'Brandon Ambuila',
url: url:
"https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E", 'https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E',
), ),
UrlListTitle( const UrlListTitle(
title: "Security status - Safe Animation", title: 'Security status - Safe Animation',
subtitle: "Yogesh Pal", subtitle: 'Yogesh Pal',
url: url:
"https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx", 'https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx',
), ),
UrlListTitle( const UrlListTitle(
title: "send mail Animation", title: 'send mail Animation',
subtitle: "jignesh gajjar", subtitle: 'jignesh gajjar',
url: "https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq", url: 'https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq',
), ),
UrlListTitle( const UrlListTitle(
title: "Present for you Animation", title: 'Present for you Animation',
subtitle: "Tatsiana Melnikova", subtitle: 'Tatsiana Melnikova',
url: url:
"https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY", 'https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY',
), ),
UrlListTitle( const UrlListTitle(
title: "Take a photo Animation", title: 'Take a photo Animation',
subtitle: "Nguyễn Như Lân", subtitle: 'Nguyễn Như Lân',
url: url:
"https://lottiefiles.com/free-animation/take-a-photo-CzOUerxwPP?color-palette=true", 'https://lottiefiles.com/free-animation/take-a-photo-CzOUerxwPP?color-palette=true',
), ),
UrlListTitle( const UrlListTitle(
title: "Valentine's Day-Animation", title: "Valentine's Day-Animation",
subtitle: "Strezha", subtitle: 'Strezha',
url: url:
"https://lottiefiles.com/de/free-animation/valentines-day-1UiMkPHnPK?color-palette=true", 'https://lottiefiles.com/de/free-animation/valentines-day-1UiMkPHnPK?color-palette=true',
), ),
UrlListTitle( const UrlListTitle(
title: "success-Animation", title: 'success-Animation',
subtitle: "Aman Awasthy", subtitle: 'Aman Awasthy',
url: url:
"https://lottiefiles.com/de/free-animation/success-tick-cuwjLHAR7g", 'https://lottiefiles.com/de/free-animation/success-tick-cuwjLHAR7g',
), ),
UrlListTitle( const UrlListTitle(
title: "Failed-Animation", title: 'Failed-Animation',
subtitle: "Ahmed Shami أحمد شامي", subtitle: 'Ahmed Shami أحمد شامي',
url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv", url: 'https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv',
), ),
const Divider(), const Divider(),
if (sticker.isNotEmpty) if (sticker.isNotEmpty)
ListTile( const ListTile(
title: Center( title: Center(
child: Text( child: Text(
"Filters", 'Filters',
style: const TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
)), )),
), ),
...sticker.map( ...sticker.map(
@ -150,9 +149,9 @@ class _CreditsViewState extends State<CreditsView> {
height: 50, height: 50,
width: 50, width: 50,
child: CachedNetworkImage( child: CachedNetworkImage(
imageUrl: "https://twonly.eu/${x.imageSrc}"), imageUrl: 'https://twonly.eu/${x.imageSrc}'),
), ),
title: "", title: '',
url: x.source, url: x.source,
), ),
), ),

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/log.dart';
@ -18,7 +18,7 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
// Assuming the button is at the bottom of the scroll view // Assuming the button is at the bottom of the scroll view
_scrollController.animateTo( _scrollController.animateTo(
_scrollController.position.maxScrollExtent, // Scroll to the bottom _scrollController.position.maxScrollExtent, // Scroll to the bottom
duration: Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
} }
@ -42,12 +42,12 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
controller: _scrollController, controller: _scrollController,
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: Text(logText), child: Text(logText),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@ -65,7 +65,8 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
final result = await SharePlus.instance.share(params); final result = await SharePlus.instance.share(params);
if (result.status != ShareResultStatus.success) { if (result.status != ShareResultStatus.success) {
Clipboard.setData(ClipboardData(text: logText)); await Clipboard.setData(
ClipboardData(text: logText));
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(

View file

@ -54,7 +54,7 @@ class NotificationView extends StatelessWidget {
final pushData = await getPushData( final pushData = await getPushData(
user.userId, user.userId,
PushNotification( PushNotification(
messageId: Int64(0), messageId: Int64(),
kind: PushKind.testNotification, kind: PushKind.testNotification,
), ),
); );

View file

@ -43,7 +43,7 @@ class _PrivacyViewState extends State<PrivacyView> {
onTap: () async { onTap: () async {
await Navigator.push(context, await Navigator.push(context,
MaterialPageRoute(builder: (context) { MaterialPageRoute(builder: (context) {
return PrivacyViewBlockUsers(); return const PrivacyViewBlockUsers();
})); }));
}, },
), ),

View file

@ -2,10 +2,10 @@ import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pie_menu/pie_menu.dart'; import 'package:pie_menu/pie_menu.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/database/daos/contacts_dao.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/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/user_context_menu.dart'; import 'package:twonly/src/views/components/user_context_menu.dart';
class PrivacyViewBlockUsers extends StatefulWidget { class PrivacyViewBlockUsers extends StatefulWidget {
@ -18,7 +18,7 @@ class PrivacyViewBlockUsers extends StatefulWidget {
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> { class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
late Stream<List<Contact>> allUsers; late Stream<List<Contact>> allUsers;
List<Contact> filteredUsers = []; List<Contact> filteredUsers = [];
String filter = ""; String filter = '';
@override @override
void initState() { void initState() {
@ -40,11 +40,12 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
body: PieCanvas( body: PieCanvas(
theme: getPieCanvasTheme(context), theme: getPieCanvasTheme(context),
child: Padding( child: Padding(
padding: EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10), padding:
const EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField( child: TextField(
onChanged: (value) => setState(() { onChanged: (value) => setState(() {
filter = value; filter = value;
@ -110,7 +111,7 @@ class UserList extends StatelessWidget {
restorationId: 'new_message_users_list', restorationId: 'new_message_users_list',
itemCount: users.length, itemCount: users.length,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
Contact user = users[i]; final user = users[i];
return UserContextMenuBlocked( return UserContextMenuBlocked(
contact: user, contact: user,
child: ListTile( child: ListTile(

View file

@ -24,9 +24,10 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
Future<void> updateUserAvatar(String json, String svg) async { Future<void> updateUserAvatar(String json, String svg) async {
await updateUserdata((user) { await updateUserdata((user) {
user.avatarJson = json; user
user.avatarSvg = svg; ..avatarJson = json
user.avatarCounter = user.avatarCounter + 1; ..avatarSvg = svg
..avatarCounter = user.avatarCounter + 1;
return user; return user;
}); });
await notifyContactsAboutProfileChange(); await notifyContactsAboutProfileChange();
@ -35,7 +36,7 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) { AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) {
if (isDarkMode(context)) { if (isDarkMode(context)) {
return AvatarMakerThemeData( return AvatarMakerThemeData(
boxDecoration: BoxDecoration( boxDecoration: const BoxDecoration(
boxShadow: [BoxShadow()], boxShadow: [BoxShadow()],
), ),
unselectedTileDecoration: BoxDecoration( unselectedTileDecoration: BoxDecoration(
@ -49,13 +50,13 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
selectedIconColor: Colors.white, selectedIconColor: Colors.white,
unselectedIconColor: Colors.grey, unselectedIconColor: Colors.grey,
primaryBgColor: Colors.black, // Dark mode background primaryBgColor: Colors.black, // Dark mode background
secondaryBgColor: Colors.grey[850]!, // Dark mode secondary background secondaryBgColor: Colors.grey[850], // Dark mode secondary background
labelTextStyle: labelTextStyle:
TextStyle(color: Colors.white), // Light text for dark mode const TextStyle(color: Colors.white), // Light text for dark mode
); );
} else { } else {
return AvatarMakerThemeData( return AvatarMakerThemeData(
boxDecoration: BoxDecoration( boxDecoration: const BoxDecoration(
boxShadow: [BoxShadow()], boxShadow: [BoxShadow()],
), ),
unselectedTileDecoration: BoxDecoration( unselectedTileDecoration: BoxDecoration(
@ -69,9 +70,9 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
selectedIconColor: Colors.black, selectedIconColor: Colors.black,
unselectedIconColor: Colors.grey, unselectedIconColor: Colors.grey,
primaryBgColor: Colors.white, // Light mode background primaryBgColor: Colors.white, // Light mode background
secondaryBgColor: Colors.grey[200]!, // Light mode secondary background secondaryBgColor: Colors.grey[200], // Light mode secondary background
labelTextStyle: labelTextStyle:
TextStyle(color: Colors.black), // Dark text for light mode const TextStyle(color: Colors.black), // Dark text for light mode
); );
} }
} }
@ -85,10 +86,9 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
body: Center( body: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 00), padding: EdgeInsets.zero,
child: AvatarMakerAvatar( child: AvatarMakerAvatar(
radius: 130, radius: 130,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@ -100,7 +100,7 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.floppyDisk), icon: const FaIcon(FontAwesomeIcons.floppyDisk),
onPressed: () async { onPressed: () async {
await _avatarMakerController.saveAvatarSVG(); await _avatarMakerController.saveAvatarSVG();
final json = final json =
@ -113,27 +113,23 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
}, },
), ),
IconButton( IconButton(
icon: FaIcon(FontAwesomeIcons.shuffle), icon: const FaIcon(FontAwesomeIcons.shuffle),
onPressed: () { onPressed:
_avatarMakerController.randomizedSelectedOptions(); _avatarMakerController.randomizedSelectedOptions,
},
), ),
IconButton( IconButton(
icon: Icon(FontAwesomeIcons.rotateLeft), icon: const Icon(FontAwesomeIcons.rotateLeft),
onPressed: () { onPressed: _avatarMakerController.restoreState,
_avatarMakerController.restoreState();
},
), ),
], ],
), ),
), ),
Padding( Padding(
padding: padding:
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 30), const EdgeInsets.symmetric(horizontal: 8, vertical: 30),
child: AvatarMakerCustomizer( child: AvatarMakerCustomizer(
scaffoldWidth: scaffoldWidth:
min(600, MediaQuery.of(context).size.width * 0.85), min(600, MediaQuery.of(context).size.width * 0.85),
autosave: false,
theme: getAvatarMakerTheme(context), theme: getAvatarMakerTheme(context),
controller: _avatarMakerController, controller: _avatarMakerController,
), ),

View file

@ -1,11 +1,11 @@
import 'package:avatar_maker/avatar_maker.dart'; import 'package:avatar_maker/avatar_maker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/views/components/better_list_title.dart';
import 'package:twonly/src/model/json/userdata.dart'; import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/services/api/messages.dart'; import 'package:twonly/src/services/api/messages.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/utils/storage.dart';
import 'package:twonly/src/views/components/better_list_title.dart';
import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart'; import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart';
class ProfileView extends StatefulWidget { class ProfileView extends StatefulWidget {
@ -33,13 +33,14 @@ class _ProfileViewState extends State<ProfileView> {
Future<void> updateUserDisplayName(String displayName) async { Future<void> updateUserDisplayName(String displayName) async {
await updateUserdata((user) { await updateUserdata((user) {
user.displayName = displayName; user
user.avatarCounter = user.avatarCounter + 1; ..displayName = displayName
..avatarCounter = user.avatarCounter + 1;
return user; return user;
}); });
await notifyContactsAboutProfileChange(); await notifyContactsAboutProfileChange();
initAsync(); await initAsync();
} }
@override @override
@ -49,34 +50,34 @@ class _ProfileViewState extends State<ProfileView> {
title: Text(context.lang.settingsProfile), title: Text(context.lang.settingsProfile),
), ),
body: ListView( body: ListView(
physics: BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
children: <Widget>[ children: <Widget>[
SizedBox(height: 25), const SizedBox(height: 25),
AvatarMakerAvatar( AvatarMakerAvatar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
radius: 80, radius: 80,
controller: _avatarMakerController, controller: _avatarMakerController,
), ),
SizedBox(height: 10), const SizedBox(height: 10),
Center( Center(
child: SizedBox( child: SizedBox(
height: 35, height: 35,
child: ElevatedButton.icon( child: ElevatedButton.icon(
icon: Icon(Icons.edit), icon: const Icon(Icons.edit),
label: Text(context.lang.settingsProfileCustomizeAvatar), label: Text(context.lang.settingsProfileCustomizeAvatar),
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ModifyAvatar(), builder: (context) => const ModifyAvatar(),
), ),
); );
_avatarMakerController.performRestore(); await _avatarMakerController.performRestore();
setState(() {}); setState(() {});
}), }),
), ),
), ),
SizedBox(height: 20), const SizedBox(height: 20),
const Divider(), const Divider(),
BetterListTile( BetterListTile(
icon: FontAwesomeIcons.userPen, icon: FontAwesomeIcons.userPen,
@ -85,8 +86,8 @@ class _ProfileViewState extends State<ProfileView> {
onTap: () async { onTap: () async {
final displayName = final displayName =
await showDisplayNameChangeDialog(context, user!.displayName); await showDisplayNameChangeDialog(context, user!.displayName);
if (context.mounted && displayName != null && displayName != "") { if (context.mounted && displayName != null && displayName != '') {
updateUserDisplayName(displayName); await updateUserDisplayName(displayName);
} }
}, },
), ),
@ -98,8 +99,7 @@ class _ProfileViewState extends State<ProfileView> {
Future<String?> showDisplayNameChangeDialog( Future<String?> showDisplayNameChangeDialog(
BuildContext context, String currentName) { BuildContext context, String currentName) {
final TextEditingController controller = final controller = TextEditingController(text: currentName);
TextEditingController(text: currentName);
return showDialog<String>( return showDialog<String>(
context: context, context: context,

View file

@ -67,7 +67,7 @@ class _SettingsMainViewState extends State<SettingsMainView> {
child: Row( child: Row(
children: [ children: [
ContactAvatar( ContactAvatar(
userData: userData!, userData: userData,
fontSize: 30, fontSize: 30,
), ),
Container(width: 20, color: Colors.transparent), Container(width: 20, color: Colors.transparent),
@ -200,7 +200,7 @@ class _SettingsMainViewState extends State<SettingsMainView> {
onTap: () { onTap: () {
Navigator.push(context, MaterialPageRoute( Navigator.push(context, MaterialPageRoute(
builder: (context) { builder: (context) {
return HelpView(); return const HelpView();
}, },
)); ));
}, },

View file

@ -18,11 +18,11 @@ class _ShareWithFriendsView extends State<ShareWithFriendsView> {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_controller.text = _controller.text =
context.lang.inviteFriendsShareText("https://twonly.eu/install"); context.lang.inviteFriendsShareText('https://twonly.eu/install');
}); });
} }
void _shareText() async { Future<void> _shareText() async {
final textToShare = _controller.text; final textToShare = _controller.text;
final params = ShareParams( final params = ShareParams(
text: textToShare, text: textToShare,
@ -37,19 +37,18 @@ class _ShareWithFriendsView extends State<ShareWithFriendsView> {
title: Text(context.lang.inviteFriends), title: Text(context.lang.inviteFriends),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
TextField( TextField(
controller: _controller, controller: _controller,
style: const TextStyle(fontSize: 14.0), style: const TextStyle(fontSize: 14),
decoration: InputDecoration(),
), ),
SizedBox(height: 16.0), const SizedBox(height: 16),
FilledButton.icon( FilledButton.icon(
onPressed: _shareText, onPressed: _shareText,
icon: FaIcon(FontAwesomeIcons.shareFromSquare), icon: const FaIcon(FontAwesomeIcons.shareFromSquare),
label: Text(context.lang.inviteFriendsShareBtn), label: Text(context.lang.inviteFriendsShareBtn),
), ),
], ],

View file

@ -117,7 +117,7 @@ class _AdditionalUsersViewState extends State<AdditionalUsersView> {
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: GridView.count( child: GridView.count(
crossAxisCount: 2, crossAxisCount: 2,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),

View file

@ -88,7 +88,7 @@ class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
), ),
onTap: toggleRenewalOption, onTap: toggleRenewalOption,
trailing: Checkbox( trailing: Checkbox(
value: autoRenewal!, value: autoRenewal,
onChanged: (a) { onChanged: (a) {
toggleRenewalOption(); toggleRenewalOption();
}, },

View file

@ -266,7 +266,7 @@ class _SubscriptionViewState extends State<SubscriptionView> {
PlanCard( PlanCard(
planId: 'Tester', planId: 'Tester',
onTap: () async { onTap: () async {
bool activate = await showAlertDialog( final activate = await showAlertDialog(
context, context,
context.lang.testingAccountTitle, context.lang.testingAccountTitle,
context.lang.testingAccountBody, context.lang.testingAccountBody,

View file

@ -4,8 +4,11 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
class TransactionView extends StatefulWidget { class TransactionView extends StatefulWidget {
const TransactionView( const TransactionView({
{super.key, required this.transactions, required this.formattedBalance}); required this.transactions,
required this.formattedBalance,
super.key,
});
final List<Response_Transaction>? transactions; final List<Response_Transaction>? transactions;
final String formattedBalance; final String formattedBalance;
@ -23,7 +26,7 @@ class _TransactionViewState extends State<TransactionView> {
body: ListView( body: ListView(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(32.0), padding: const EdgeInsets.all(32),
child: Center( child: Center(
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -51,10 +54,9 @@ class _TransactionViewState extends State<TransactionView> {
} }
class TransactionCard extends StatefulWidget { class TransactionCard extends StatefulWidget {
const TransactionCard({required this.transaction, super.key});
final Response_Transaction transaction; final Response_Transaction transaction;
const TransactionCard({super.key, required this.transaction});
@override @override
State<TransactionCard> createState() => _TransactionCardState(); State<TransactionCard> createState() => _TransactionCardState();
} }

View file

@ -111,7 +111,7 @@ class _VoucherCardState extends State<VoucherCard> {
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
elevation: 5, elevation: 5,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -160,7 +160,7 @@ Future<void> redeemVoucher(BuildContext context) async {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16),
child: TextField( child: TextField(
onChanged: (value) { onChanged: (value) {
// Convert to uppercase // Convert to uppercase

View file

@ -76,7 +76,6 @@ TargetFocus getTargetFocus(
left: 0, right: 0, top: top, bottom: bottom), left: 0, right: 0, top: top, bottom: bottom),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text( Text(

View file

@ -11,17 +11,18 @@ Future<void> showChatListTutorialSearchOtherUsers(
GlobalKey searchForOtherUsers, GlobalKey searchForOtherUsers,
) async { ) async {
await lockDisplayTutorial.protect(() async { await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown("chat_list:search_users")) { if (await checkIfTutorialAlreadyShown('chat_list:search_users')) {
return; return;
} }
if (!context.mounted) return; if (!context.mounted) return;
List<TargetFocus> targets = []; final targets = <TargetFocus>[
targets.add(getTargetFocus( getTargetFocus(
context, context,
searchForOtherUsers, searchForOtherUsers,
context.lang.tutorialChatListSearchUsersTitle, context.lang.tutorialChatListSearchUsersTitle,
context.lang.tutorialChatListSearchUsersDesc, context.lang.tutorialChatListSearchUsersDesc,
)); )
];
await showTutorial(context, targets); await showTutorial(context, targets);
}); });
} }
@ -31,17 +32,18 @@ Future<void> showChatListTutorialContextMenu(
GlobalKey firstUserListItemKey, GlobalKey firstUserListItemKey,
) async { ) async {
await lockDisplayTutorial.protect(() async { await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown("chat_list:context_menu")) { if (await checkIfTutorialAlreadyShown('chat_list:context_menu')) {
return; return;
} }
if (!context.mounted) return; if (!context.mounted) return;
List<TargetFocus> targets = []; final targets = <TargetFocus>[
targets.add(getTargetFocus( getTargetFocus(
context, context,
firstUserListItemKey, firstUserListItemKey,
context.lang.tutorialChatListContextMenuTitle, context.lang.tutorialChatListContextMenuTitle,
context.lang.tutorialChatListContextMenuDesc, context.lang.tutorialChatListContextMenuDesc,
)); )
];
await showTutorial(context, targets); await showTutorial(context, targets);
}); });
} }
@ -51,17 +53,18 @@ Future<void> showVerifyShieldTutorial(
GlobalKey firstUserListItemKey, GlobalKey firstUserListItemKey,
) async { ) async {
await lockDisplayTutorial.protect(() async { await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown("chat_messages:verify_shield")) { if (await checkIfTutorialAlreadyShown('chat_messages:verify_shield')) {
return; return;
} }
if (!context.mounted) return; if (!context.mounted) return;
List<TargetFocus> targets = []; final targets = <TargetFocus>[
targets.add(getTargetFocus( getTargetFocus(
context, context,
firstUserListItemKey, firstUserListItemKey,
context.lang.tutorialChatMessagesVerifyShieldTitle, context.lang.tutorialChatMessagesVerifyShieldTitle,
context.lang.tutorialChatMessagesVerifyShieldDesc, context.lang.tutorialChatMessagesVerifyShieldDesc,
)); )
];
await showTutorial(context, targets); await showTutorial(context, targets);
}); });
} }
@ -71,17 +74,18 @@ Future<void> showReopenMediaFilesTutorial(
GlobalKey firstUserListItemKey, GlobalKey firstUserListItemKey,
) async { ) async {
await lockDisplayTutorial.protect(() async { await lockDisplayTutorial.protect(() async {
if (await checkIfTutorialAlreadyShown("chat_messages:reopen_message")) { if (await checkIfTutorialAlreadyShown('chat_messages:reopen_message')) {
return; return;
} }
if (!context.mounted) return; if (!context.mounted) return;
List<TargetFocus> targets = []; final targets = <TargetFocus>[
targets.add(getTargetFocus( getTargetFocus(
context, context,
firstUserListItemKey, firstUserListItemKey,
context.lang.tutorialChatMessagesReopenMessageTitle, context.lang.tutorialChatMessagesReopenMessageTitle,
context.lang.tutorialChatMessagesReopenMessageDesc, context.lang.tutorialChatMessagesReopenMessageDesc,
)); )
];
await showTutorial(context, targets); await showTutorial(context, targets);
}); });
} }

View file

@ -10,12 +10,20 @@ environment:
sdk: ^3.6.0 sdk: ^3.6.0
dependencies: dependencies:
avatar_maker: ^0.4.0
background_downloader: ^9.2.2
cached_network_image: ^3.4.1
camera: ^0.11.1
collection: ^1.18.0 collection: ^1.18.0
connectivity_plus: ^6.1.2 connectivity_plus: ^6.1.2
cryptography_flutter_plus: ^2.3.4
cryptography_plus: ^2.7.0
device_info_plus: ^11.5.0
drift: ^2.25.1 drift: ^2.25.1
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4
firebase_core: ^3.11.0 firebase_core: ^3.11.0
firebase_messaging: ^15.2.2 firebase_messaging: ^15.2.2
fixnum: ^1.1.1
flutter: flutter:
sdk: flutter sdk: flutter
flutter_image_compress: ^2.4.0 flutter_image_compress: ^2.4.0
@ -25,15 +33,17 @@ dependencies:
# flutter_secure_storage: ^10.0.0-beta.4 # flutter_secure_storage: ^10.0.0-beta.4
flutter_secure_storage: flutter_secure_storage:
path: ./dependencies/flutter_secure_storage/flutter_secure_storage path: ./dependencies/flutter_secure_storage/flutter_secure_storage
flutter_svg: ^2.0.17
flutter_zxing: flutter_zxing:
path: ./dependencies/flutter_zxing path: ./dependencies/flutter_zxing
# pie_menu: ^3.2.7
pie_menu:
path: ./dependencies/flutter-pie-menu
font_awesome_flutter: ^10.8.0 font_awesome_flutter: ^10.8.0
gal: ^2.3.1 gal: ^2.3.1
get: ^4.7.2
hand_signature: ^3.0.3 hand_signature: ^3.0.3
hashlib: ^2.0.0
http: ^1.3.0
image: ^4.3.0 image: ^4.3.0
image_picker: ^1.1.2
intl: ^0.20.2 intl: ^0.20.2
introduction_screen: ^3.1.14 introduction_screen: ^3.1.14
json_annotation: ^4.9.0 json_annotation: ^4.9.0
@ -41,46 +51,35 @@ dependencies:
local_auth: ^2.3.0 local_auth: ^2.3.0
logging: ^1.3.0 logging: ^1.3.0
lottie: ^3.3.1 lottie: ^3.3.1
mutex: ^3.1.0
no_screenshot: ^0.3.1 no_screenshot: ^0.3.1
package_info_plus: ^8.2.1 package_info_plus: ^8.2.1
path: ^1.9.0 path: ^1.9.0
path_provider: ^2.1.5 path_provider: ^2.1.5
permission_handler: ^12.0.0+1 permission_handler: ^12.0.0+1
photo_view: ^0.15.0
pie_menu:
path: ./dependencies/flutter-pie-menu
protobuf: ^4.0.0 protobuf: ^4.0.0
cryptography_plus: ^2.7.0
provider: ^6.1.2 provider: ^6.1.2
restart_app: ^1.3.2 restart_app: ^1.3.2
screenshot: ^3.0.0 screenshot: ^3.0.0
url_launcher: ^6.3.1
web_socket_channel: ^3.0.1
camera: ^0.11.1
avatar_maker: ^0.4.0
flutter_svg: ^2.0.17
fixnum: ^1.1.1
mutex: ^3.1.0
cached_network_image: ^3.4.1
image_picker: ^1.1.2
http: ^1.3.0
get: ^4.7.2
video_player: ^2.9.5
video_compress: ^3.1.4
share_plus: ^11.0.0 share_plus: ^11.0.0
photo_view: ^0.15.0
tutorial_coach_mark: ^1.3.0 tutorial_coach_mark: ^1.3.0
background_downloader: ^9.2.2 url_launcher: ^6.3.1
hashlib: ^2.0.0 video_compress: ^3.1.4
video_player: ^2.9.5
video_thumbnail: ^0.5.6 video_thumbnail: ^0.5.6
device_info_plus: ^11.5.0 web_socket_channel: ^3.0.1
cryptography_flutter_plus: ^2.3.4
dev_dependencies: dev_dependencies:
build_runner: ^2.4.15
drift_dev: ^2.25.2
flutter_launcher_icons: ^0.14.1
flutter_lints: ^6.0.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.15
json_serializable: ^6.8.0 json_serializable: ^6.8.0
flutter_lints: ^6.0.0
flutter_launcher_icons: ^0.14.1
drift_dev: ^2.25.2
very_good_analysis: ^9.0.0 very_good_analysis: ^9.0.0
flutter_launcher_icons: flutter_launcher_icons:

View file

@ -4,10 +4,10 @@ import 'package:twonly/src/views/components/animate_icon.dart';
void main() { void main() {
group('isEmoji', () { group('isEmoji', () {
test('test isEmoji function', () { test('test isEmoji function', () {
expect(isEmoji("Hallo"), false); expect(isEmoji('Hallo'), false);
expect(isEmoji("😂"), true); expect(isEmoji('😂'), true);
expect(isEmoji("😂😂"), false); expect(isEmoji('😂😂'), false);
expect(isEmoji("Hallo 😂"), false); expect(isEmoji('Hallo 😂'), false);
}); });
}); });
} }