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/api/websocket/**"
- "lib/generated/**"
- "dependencies/**"
- "test/drift/**"
- "**.g.dart"

View file

@ -106,7 +106,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
Locale('en', ''),
Locale('de', ''),
],
onGenerateTitle: (BuildContext context) => "twonly",
onGenerateTitle: (BuildContext context) => 'twonly',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF57CC99),
@ -132,8 +132,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
themeMode: context.watch<SettingsChangeProvider>().themeMode,
initialRoute: '/',
routes: {
"/": (context) => AppMainWidget(initialPage: 1),
"/chats": (context) => AppMainWidget(initialPage: 0)
'/': (context) => const AppMainWidget(initialPage: 1),
'/chats': (context) => const AppMainWidget(initialPage: 0)
},
);
},
@ -143,8 +143,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
class AppMainWidget extends StatefulWidget {
const AppMainWidget({
super.key,
required this.initialPage,
super.key,
});
final int initialPage;
@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(
int userId, ContactsCompanion updatedValues) async {
await ((update(contacts)..where((c) => c.userId.equals(userId)))
.write(updatedValues));
await (update(contacts)..where((c) => c.userId.equals(userId)))
.write(updatedValues);
if (updatedValues.blocked.present ||
updatedValues.displayName.present ||
updatedValues.nickName.present) {
@ -177,8 +177,9 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
Stream<int?> watchContactsBlocked() {
final count = contacts.userId.count();
final query = selectOnly(contacts)..where(contacts.blocked.equals(true));
query.addColumns([count]);
final query = selectOnly(contacts)
..where(contacts.blocked.equals(true))
..addColumns([count]);
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 query = selectOnly(contacts)
..where(contacts.requested.equals(true) &
contacts.accepted.equals(true).not());
query.addColumns([count]);
contacts.accepted.equals(true).not())
..addColumns([count]);
return query.map((row) => row.read(count)).watchSingle();
}
@ -204,15 +205,13 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
u.lastMessageSend.isNotNull(),
))
.watchSingle()
.asyncMap((contact) {
return getFlameCounterFromContact(contact);
});
.asyncMap(getFlameCounterFromContact);
}
}
String getContactDisplayName(Contact user) {
String name = user.username;
if (user.nickName != null && user.nickName != "") {
var name = user.username;
if (user.nickName != null && user.nickName != '') {
name = user.nickName!;
} else if (user.displayName != null) {
name = user.displayName!;
@ -224,7 +223,7 @@ String getContactDisplayName(Contact user) {
}
String applyStrikethrough(String text) {
return text.split('').map((char) => '$char\u0336').join('');
return text.split('').map((char) => '$char\u0336').join();
}
int getFlameCounterFromContact(Contact contact) {
@ -233,7 +232,7 @@ int getFlameCounterFromContact(Contact contact) {
}
final now = DateTime.now();
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) &&
contact.lastMessageReceived!.isAfter(twoDaysAgo)) {
return contact.flameCounter + 1;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,16 +44,18 @@ class Messages extends Table {
IntColumn get responseToMessageId => integer().nullable()();
IntColumn get responseToOtherMessageId => integer().nullable()();
BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))();
BoolColumn get mediaStored => boolean().withDefault(Constant(false))();
BoolColumn get acknowledgeByUser =>
boolean().withDefault(const Constant(false))();
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
IntColumn get downloadState => intEnum<DownloadState>()
.withDefault(Constant(DownloadState.downloaded.index))();
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>()
.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:typed_data';
import 'package:json_annotation/json_annotation.dart';
part 'signal_identity.g.dart';
@JsonSerializable()
@ -9,13 +11,13 @@ class SignalIdentity {
required this.identityKeyPairU8List,
required this.registrationId,
});
factory SignalIdentity.fromJson(Map<String, dynamic> json) =>
_$SignalIdentityFromJson(json);
final int registrationId;
@Uint8ListConverter()
final Uint8List identityKeyPairU8List;
factory SignalIdentity.fromJson(Map<String, dynamic> json) =>
_$SignalIdentityFromJson(json);
Map<String, dynamic> toJson() => _$SignalIdentityToJson(this);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -37,8 +37,8 @@ Result asResult(server.ServerToClient? msg) {
}
ClientToServer createClientToServerFromHandshake(Handshake handshake) {
var v0 = client.V0()
..seq = Int64(0)
final v0 = client.V0()
..seq = Int64()
..handshake = handshake;
return ClientToServer()..v0 = v0;
}
@ -46,7 +46,7 @@ ClientToServer createClientToServerFromHandshake(Handshake handshake) {
ClientToServer createClientToServerFromApplicationData(
ApplicationData applicationData) {
final v0 = client.V0()
..seq = Int64(0)
..seq = Int64()
..applicationdata = applicationData;
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/tables/messages_table.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/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/model/json/message.dart' as my;
Future<void> syncFlameCounters() async {
var user = await getUser();
final user = await getUser();
if (user == null) return;
List<Contact> contacts =
await twonlyDB.contactsDao.getAllNotBlockedContacts();
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
if (contacts.isEmpty) return;
int maxMessageCounter = contacts.map((x) => x.totalMediaCounter).max;
Contact bestFriend =
final maxMessageCounter = contacts.map((x) => x.totalMediaCounter).max;
final bestFriend =
contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
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.lastFlameSync != null) {
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
if (flameCounter < 1 && bestFriend.userId != contact.userId) continue;

View file

@ -1,7 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_svg/svg.dart';
import 'package:path_provider/path_provider.dart';
@ -29,24 +28,18 @@ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
int id = 0;
Future<void> setupPushNotification() async {
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings("ic_launcher_foreground");
const initializationSettingsAndroid =
AndroidInitializationSettings('ic_launcher_foreground');
final List<DarwinNotificationCategory> darwinNotificationCategories =
<DarwinNotificationCategory>[];
final darwinNotificationCategories = <DarwinNotificationCategory>[];
/// Note: permissions aren't requested here just to demonstrate that can be
/// done later
final DarwinInitializationSettings initializationSettingsDarwin =
DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
requestProvisionalPermission: false,
final initializationSettingsDarwin = DarwinInitializationSettings(
notificationCategories: darwinNotificationCategories,
);
final InitializationSettings initializationSettings = InitializationSettings(
final initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin,
);
@ -65,23 +58,22 @@ Future<void> createPushAvatars() async {
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
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);
final ui.Image image = await pictureInfo.picture.toImage(300, 300);
final image = await pictureInfo.picture.toImage(300, 300);
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
final Uint8List pngBytes = byteData!.buffer.asUint8List();
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
final pngBytes = byteData!.buffer.asUint8List();
// Get the directory to save the image
final directory = await getApplicationCacheDirectory();
final avatarsDirectory = Directory('${directory.path}/avatars');
// Create the avatars directory if it does not exist
if (!await avatarsDirectory.exists()) {
if (!avatarsDirectory.existsSync()) {
await avatarsDirectory.create(recursive: true);
}
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))
.serialize(),
1,
);
final Uint8List? tempSignedPreKeySignature = Uint8List.fromList(
final tempSignedPreKeySignature = Uint8List.fromList(
signedPreKey.signedPreKeySignature,
);
final IdentityKey? tempIdentityKey =
await signalStore.getIdentity(address);
final tempIdentityKey = await signalStore.getIdentity(address);
if (tempIdentityKey != null) {
final preKeyBundle = PreKeyBundle(
target,
@ -79,9 +78,9 @@ Future<Uint8List?> signalEncryptMessage(
final ciphertext = await session.encrypt(plaintextContent);
final b = BytesBuilder();
b.add(ciphertext.serialize());
b.add(intToBytes(ciphertext.getType()));
final b = BytesBuilder()
..add(ciphertext.serialize())
..add(intToBytes(ciphertext.getType()));
return b.takeBytes();
} catch (e) {

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:hashlib/hashlib.dart';
@ -21,7 +22,7 @@ Future<void> enableTwonlySafe(String password) async {
);
return user;
});
performTwonlySafeBackup(force: true);
unawaited(performTwonlySafeBackup(force: true));
}
Future<void> disableTwonlySafe() async {
@ -35,9 +36,9 @@ Future<void> disableTwonlySafe() async {
// Add any other headers if required
},
);
Log.info("Download deleted with: ${response.statusCode}");
Log.info('Download deleted with: ${response.statusCode}');
} catch (e) {
Log.error("Could not connect to the server.");
Log.error('Could not connect to the server.');
}
}
await updateUserdata((user) {
@ -50,21 +51,18 @@ Future<(Uint8List, Uint8List)> getMasterKey(
String password,
String username,
) async {
List<int> passwordBytes = utf8.encode(password);
List<int> saltBytes = utf8.encode(username);
final List<int> passwordBytes = utf8.encode(password);
final List<int> saltBytes = utf8.encode(username);
// Values are derived from the Threema Whitepaper
// https://threema.com/assets/documents/cryptography_whitepaper.pdf
final scrypt = Scrypt(
cost: 65536,
blockSize: 8,
parallelism: 1,
derivedKeyLength: 64,
salt: saltBytes,
);
final key = (scrypt.convert(passwordBytes)).bytes;
final key = scrypt.convert(passwordBytes).bytes;
return (key.sublist(0, 32), key.sublist(32, 64));
}
@ -81,13 +79,13 @@ Future<String?> getTwonlySafeBackupUrlFromServer(
List<int> backupId,
BackupServer? backupServer,
) async {
String backupServerUrl = "https://safe.twonly.eu/";
var backupServerUrl = 'https://safe.twonly.eu/';
if (backupServer != null) {
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:io';
import 'package:background_downloader/background_downloader.dart';
@ -31,8 +33,8 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
return;
}
final DateTime? lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
if (!force && lastUpdateTime != null) {
final lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
if (force != true && lastUpdateTime != null) {
if (lastUpdateTime
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
return;
@ -179,8 +181,6 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
file: encryptedBackupBytesFile,
httpRequestMethod: 'PUT',
url: (await getTwonlySafeBackupUrl())!,
// requiresWiFi: true,
priority: 5,
post: 'binary',
retries: 2,
headers: {

View file

@ -36,8 +36,8 @@ Future<String> loadLogFile() async {
final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log');
if (await logFile.exists()) {
return await logFile.readAsString();
if (logFile.existsSync()) {
return logFile.readAsString();
} else {
return 'Log file does not exist.';
}
@ -51,7 +51,7 @@ Future<void> _writeLogToFile(LogRecord record) async {
final logMessage =
'${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
await logFile.writeAsString(logMessage, mode: FileMode.append);
});
@ -61,7 +61,7 @@ Future<bool> deleteLogFile() async {
final directory = await getApplicationSupportDirectory();
final logFile = File('${directory.path}/app.log');
if (await logFile.exists()) {
if (logFile.existsSync()) {
await logFile.delete();
return true;
}
@ -69,18 +69,18 @@ Future<bool> deleteLogFile() async {
}
String _getCallerSourceCodeFilename() {
StackTrace stackTrace = StackTrace.current;
String stackTraceString = stackTrace.toString();
String fileName = "";
String lineNumber = "";
List<String> stackLines = stackTraceString.split('\n');
final stackTrace = StackTrace.current;
final stackTraceString = stackTrace.toString();
var fileName = '';
var lineNumber = '';
final stackLines = stackTraceString.split('\n');
if (stackLines.length > 2) {
String callerInfo = stackLines[2];
List<String> parts = callerInfo.split('/');
final callerInfo = stackLines[2];
final parts = callerInfo.split('/');
fileName = parts.last.split(':').first; // Extract the file name
lineNumber = parts.last.split(':')[1]; // Extract the line number
} else {
String firstLine = stackTraceString.split('\n')[0];
final firstLine = stackTraceString.split('\n')[0];
fileName =
firstLine.split('/').last.split(':').first; // Extract the file name
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:permission_handler/permission_handler.dart';
import 'package:twonly/src/utils/log.dart';
class PermissionHandlerView extends StatefulWidget {
const PermissionHandlerView({super.key, required this.onSuccess});
const PermissionHandlerView({required this.onSuccess, super.key});
final Function onSuccess;
@ -25,7 +27,7 @@ Future<bool> checkPermissions() async {
class PermissionHandlerViewState extends State<PermissionHandlerView> {
Future<Map<Permission, PermissionStatus>> permissionServices() async {
// try {
Map<Permission, PermissionStatus> statuses = await [
final statuses = await [
Permission.camera,
// Permission.microphone,
Permission.notification
@ -57,18 +59,17 @@ class PermissionHandlerViewState extends State<PermissionHandlerView> {
return Scaffold(
body: Center(
child: Container(
padding: EdgeInsets.all(100),
padding: const EdgeInsets.all(100),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"twonly needs access to the camera and microphone.",
const Text(
'twonly needs access to the camera and microphone.',
textAlign: TextAlign.center,
),
SizedBox(height: 50),
const SizedBox(height: 50),
FilledButton.icon(
label: Text("Request permissions"),
label: const Text('Request permissions'),
icon: const Icon(Icons.perm_camera_mic),
onPressed: () async {
try {

View file

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

View file

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

View file

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
class VideoRecordingTimer extends StatelessWidget {
final DateTime? videoRecordingStarted;
final int maxVideoRecordingTime;
const VideoRecordingTimer({
super.key,
required this.videoRecordingStarted,
required this.maxVideoRecordingTime,
super.key,
});
final DateTime? videoRecordingStarted;
final int maxVideoRecordingTime;
@override
Widget build(BuildContext context) {
@ -26,11 +25,12 @@ class VideoRecordingTimer extends StatelessWidget {
children: [
Center(
child: CircularProgressIndicator(
value: (currentTime.difference(videoRecordingStarted!))
value: currentTime
.difference(videoRecordingStarted!)
.inMilliseconds /
(maxVideoRecordingTime * 1000),
strokeWidth: 4,
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
valueColor: const AlwaysStoppedAnimation<Color>(Colors.red),
backgroundColor: Colors.grey[300],
),
),
@ -46,7 +46,7 @@ class VideoRecordingTimer extends StatelessWidget {
shadows: [
Shadow(
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 'package:camera/camera.dart';
import 'package:flutter/material.dart';
class CameraZoomButtons extends StatefulWidget {
const CameraZoomButtons(
{super.key,
required this.controller,
required this.updateScaleFactor,
required this.scaleFactor});
const CameraZoomButtons({
required this.controller,
required this.updateScaleFactor,
required this.scaleFactor,
super.key,
});
final CameraController controller;
final double scaleFactor;
@ -20,7 +23,7 @@ class CameraZoomButtons extends StatefulWidget {
String beautifulZoomScale(double scale) {
var tmp = scale.toStringAsFixed(1);
if (tmp[0] == "0") {
if (tmp[0] == '0') {
tmp = tmp.substring(1, tmp.length);
}
return tmp;
@ -50,20 +53,20 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
@override
Widget build(BuildContext context) {
var zoomButtonStyle = TextButton.styleFrom(
final zoomButtonStyle = TextButton.styleFrom(
padding: EdgeInsets.zero,
foregroundColor: Colors.white,
minimumSize: Size(40, 40),
minimumSize: const Size(40, 40),
alignment: Alignment.center,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
);
final zoomTextStyle = TextStyle(fontSize: 13);
const zoomTextStyle = TextStyle(fontSize: 13);
final isMiddleFocused = widget.scaleFactor >= 1 && widget.scaleFactor < 2;
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(40.0),
child: Container(
borderRadius: BorderRadius.circular(40),
child: ColoredBox(
color: const Color.fromARGB(90, 0, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
@ -76,25 +79,24 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
),
),
onPressed: () async {
var level = await widget.controller.getMinZoomLevel();
final level = await widget.controller.getMinZoomLevel();
widget.updateScaleFactor(level);
},
child: FutureBuilder(
future: widget.controller.getMinZoomLevel(),
builder: (context, snap) {
if (snap.hasData) {
var minLevel =
beautifulZoomScale(snap.data!.toDouble());
var currentLevel =
final minLevel = beautifulZoomScale(snap.data!);
final currentLevel =
beautifulZoomScale(widget.scaleFactor);
return Text(
widget.scaleFactor < 1
? "${currentLevel}x"
: "${minLevel}x",
? '${currentLevel}x'
: '${minLevel}x',
style: zoomTextStyle,
);
} else {
return Text("");
return const Text('');
}
},
),
@ -109,9 +111,9 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
widget.updateScaleFactor(1.0);
},
child: Text(
(isMiddleFocused)
? "${beautifulZoomScale(widget.scaleFactor)}x"
: "1.0x",
isMiddleFocused
? '${beautifulZoomScale(widget.scaleFactor)}x'
: '1.0x',
style: zoomTextStyle,
)),
TextButton(
@ -121,23 +123,24 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
),
),
onPressed: () async {
var level = min(await widget.controller.getMaxZoomLevel(), 2)
.toDouble();
final level =
min(await widget.controller.getMaxZoomLevel(), 2)
.toDouble();
widget.updateScaleFactor(level);
},
child: FutureBuilder(
future: widget.controller.getMaxZoomLevel(),
builder: (context, snap) {
if (snap.hasData) {
var maxLevel = max(
final maxLevel = max(
min((snap.data?.toInt())!, 2),
widget.scaleFactor,
);
return Text(
"${beautifulZoomScale(maxLevel.toDouble())}x",
'${beautifulZoomScale(maxLevel.toDouble())}x',
style: zoomTextStyle);
} else {
return Text("");
return const Text('');
}
}),
)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,18 +1,13 @@
// ignore_for_file: comment_references
import 'package:flutter/material.dart';
import 'package:hand_signature/signature.dart';
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
/// Layer class with some common properties
class Layer {
Offset offset;
double rotation, scale, opacity;
bool isEditing;
bool isDeleted;
bool hasCustomActionButtons;
bool showCustomButtons;
Layer({
this.offset = const Offset(0, 0),
this.offset = Offset.zero,
this.opacity = 1,
this.isEditing = false,
this.isDeleted = false,
@ -21,24 +16,28 @@ class Layer {
this.rotation = 0,
this.scale = 1,
});
Offset offset;
double rotation;
double scale;
double opacity;
bool isEditing;
bool isDeleted;
bool hasCustomActionButtons;
bool showCustomButtons;
}
/// Attributes used by [BackgroundLayer]
class BackgroundLayerData extends Layer {
ImageItem image;
BackgroundLayerData({
required this.image,
});
ImageItem image;
}
class FilterLayerData extends Layer {}
/// Attributes used by [EmojiLayer]
class EmojiLayerData extends Layer {
String text;
double size;
EmojiLayerData({
this.text = '',
this.size = 64,
@ -48,31 +47,27 @@ class EmojiLayerData extends Layer {
super.scale,
super.isEditing,
});
String text;
double size;
}
/// Attributes used by [TextLayer]
class TextLayerData extends Layer {
String text;
int textLayersBefore;
TextLayerData({
this.text = "",
required this.textLayersBefore,
this.text = '',
super.offset,
super.opacity,
super.rotation,
super.scale,
super.isEditing = true,
});
String text;
int textLayersBefore;
}
/// Attributes used by [DrawLayer]
class DrawLayerData extends Layer {
final control = HandSignatureControl(
threshold: 3.0,
smoothRatio: 0.65,
velocityRange: 2.0,
);
// String text;
DrawLayerData({
super.offset,
@ -82,4 +77,5 @@ class DrawLayerData extends Layer {
super.hasCustomActionButtons = 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
class BackgroundLayer extends StatefulWidget {
final BackgroundLayerData layerData;
final VoidCallback? onUpdate;
const BackgroundLayer({
super.key,
required this.layerData,
super.key,
this.onUpdate,
});
final BackgroundLayerData layerData;
final VoidCallback? onUpdate;
@override
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:hand_signature/signature.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/data/layer.dart';
import 'package:twonly/src/utils/misc.dart';
class DrawLayer extends StatefulWidget {
final DrawLayerData layerData;
final VoidCallback? onUpdate;
const DrawLayer({
super.key,
required this.layerData,
super.key,
this.onUpdate,
});
final DrawLayerData layerData;
final VoidCallback? onUpdate;
@override
createState() => _DrawLayerState();
State<DrawLayer> createState() => _DrawLayerState();
}
class _DrawLayerState extends State<DrawLayer> {
Color currentColor = Colors.red;
var screenshotController = ScreenshotController();
ScreenshotController screenshotController = ScreenshotController();
List<CubicPath> undoList = [];
bool skipNextEvent = false;
@ -48,7 +47,7 @@ class _DrawLayerState extends State<DrawLayer> {
double _sliderValue = 0.125;
final colors = [
final List<Color> colors = [
Colors.white,
Colors.red,
Colors.orange,
@ -61,11 +60,11 @@ class _DrawLayerState extends State<DrawLayer> {
Color _getColorFromSliderValue(double value) {
// Calculate the index based on the slider value
int index = (value * (colors.length - 1)).floor();
int nextIndex = (index + 1).clamp(0, colors.length - 1);
final index = (value * (colors.length - 1)).floor();
final nextIndex = (index + 1).clamp(0, colors.length - 1);
// Calculate the interpolation factor
double factor = value * (colors.length - 1) - index;
final factor = value * (colors.length - 1) - index;
// Interpolate between the two colors
return Color.lerp(colors[index], colors[nextIndex], factor)!;
@ -85,17 +84,14 @@ class _DrawLayerState extends State<DrawLayer> {
children: [
Positioned.fill(
child: Container(
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: Colors.transparent,
),
child: Screenshot(
controller: screenshotController,
child: HandSignature(
control: widget.layerData.control,
color: currentColor,
width: 1.0,
maxWidth: 7.0,
type: SignatureDrawType.shape,
drawer: LineSignatureDrawer(color: currentColor, width: 7),
),
),
),
@ -112,9 +108,7 @@ class _DrawLayerState extends State<DrawLayer> {
tooltipText: context.lang.imageEditorDrawOk,
onPressed: () async {
widget.layerData.isEditing = false;
if (widget.onUpdate != null) {
widget.onUpdate!();
}
widget.onUpdate!();
setState(() {});
},
),
@ -200,8 +194,6 @@ class _DrawLayerState extends State<DrawLayer> {
showMagnifyingGlass = false;
})
},
min: 0.0,
max: 1.0,
divisions: 100,
),
),
@ -226,10 +218,9 @@ class _DrawLayerState extends State<DrawLayer> {
}
class MagnifyingGlass extends StatelessWidget {
const MagnifyingGlass({required this.color, super.key});
final Color color;
const MagnifyingGlass({super.key, required this.color});
@override
Widget build(BuildContext context) {
return SizedBox(

View file

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

View file

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

View file

@ -9,8 +9,8 @@ class DateTimeFilter extends StatelessWidget {
@override
Widget build(BuildContext context) {
String currentTime = DateFormat('HH:mm').format(DateTime.now());
String currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now());
final currentTime = DateFormat('HH:mm').format(DateTime.now());
final currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now());
return FilterSkeleton(
child: Positioned(
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';
class ImageFilter extends StatelessWidget {
const ImageFilter({super.key, required this.imagePath});
const ImageFilter({required this.imagePath, super.key});
final String imagePath;
@override
@ -15,7 +15,7 @@ class ImageFilter extends StatelessWidget {
right: 10,
child: Center(
child: CachedNetworkImage(
imageUrl: "https://twonly.eu/$imagePath",
imageUrl: 'https://twonly.eu/$imagePath',
height: 150,
),
),

View file

@ -99,7 +99,6 @@ class _LocationFilterState extends State<LocationFilter> {
bottom: 50,
left: 40,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FilterText(location!.city),
FilterText(location!.county),
@ -131,8 +130,8 @@ Future<List<Sticker>> getStickerIndex() async {
final indexFile = File('${directory.path}/stickers.json');
var res = <Sticker>[];
if (await indexFile.exists() && !kDebugMode) {
final lastModified = await indexFile.lastModified();
if (indexFile.existsSync() && !kDebugMode) {
final lastModified = indexFile.lastModifiedSync();
final difference = DateTime.now().difference(lastModified);
final content = await indexFile.readAsString();
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/services.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
class TextLayer extends StatefulWidget {
final TextLayerData layerData;
final VoidCallback? onUpdate;
const TextLayer({
super.key,
required this.layerData,
super.key,
this.onUpdate,
});
final TextLayerData layerData;
final VoidCallback? onUpdate;
@override
createState() => _TextViewState();
State<TextLayer> createState() => _TextViewState();
}
class _TextViewState extends State<TextLayer> {
@ -69,7 +70,7 @@ class _TextViewState extends State<TextLayer> {
minLines: 1,
onEditingComplete: () {
setState(() {
widget.layerData.isDeleted = textController.text == "";
widget.layerData.isDeleted = textController.text == '';
widget.layerData.isEditing = false;
widget.layerData.text = textController.text;
});
@ -83,10 +84,10 @@ class _TextViewState extends State<TextLayer> {
},
onTapOutside: (a) {
widget.layerData.text = textController.text;
Future.delayed(Duration(milliseconds: 100), () {
Future.delayed(const Duration(milliseconds: 100), () {
if (context.mounted) {
setState(() {
widget.layerData.isDeleted = textController.text == "";
widget.layerData.isDeleted = textController.text == '';
widget.layerData.isEditing = false;
context
.read<ImageEditorProvider>()
@ -102,10 +103,10 @@ class _TextViewState extends State<TextLayer> {
.read<ImageEditorProvider>()
.updateSomeTextViewIsAlreadyEditing(false);
},
decoration: InputDecoration(
decoration: const InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: const EdgeInsets.symmetric(
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
@ -136,7 +137,7 @@ class _TextViewState extends State<TextLayer> {
onScaleEnd: (d) {
if (deleteLayer) {
widget.layerData.isDeleted = true;
textController.text = "";
textController.text = '';
}
elementIsScaled = false;
if (widget.onUpdate != null) {
@ -161,8 +162,8 @@ class _TextViewState extends State<TextLayer> {
widget.layerData.offset = Offset(
0, widget.layerData.offset.dy + detail.focalPointDelta.dy);
}
final RenderBox renderBox =
_widgetKey.currentContext!.findRenderObject() as RenderBox;
final renderBox =
_widgetKey.currentContext!.findRenderObject()! as RenderBox;
if (widget.layerData.offset.dy > renderBox.size.height - 80) {
if (!deleteLayer) {
@ -198,11 +199,11 @@ class _TextViewState extends State<TextLayer> {
child: Center(
child: GestureDetector(
onTapUp: (d) {
textController.text = "";
textController.text = '';
},
child: ActionButton(
FontAwesomeIcons.trashCan,
tooltipText: "",
tooltipText: '',
color: deleteLayer ? Colors.red : Colors.white,
),
),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,11 +8,10 @@ class DemoUserCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
return ColoredBox(
color: isDarkMode(context) ? Colors.white : Colors.black,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'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(() {
textReactionsToMessageId = tmpTextReactionsToMessageId;
@ -271,7 +272,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
),
const SizedBox(width: 10),
Expanded(
child: Container(
child: ColoredBox(
color: Colors.transparent,
child: Row(
children: [

View file

@ -1,25 +1,24 @@
import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.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/twonly_database.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/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';
class ChatMediaEntry extends StatefulWidget {
const ChatMediaEntry({
super.key,
required this.message,
required this.contact,
required this.content,
required this.galleryItems,
super.key,
});
final Message message;
@ -47,13 +46,13 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
widget.message.mediaStored) {
return;
}
if (await received.existsMediaFile(widget.message.messageId, "png")) {
if (await received.existsMediaFile(widget.message.messageId, 'png')) {
if (mounted) {
setState(() {
canBeReopened = true;
});
}
Future.delayed(Duration(seconds: 1), () {
Future.delayed(const Duration(seconds: 1), () {
if (!mounted) return;
showReopenMediaFilesTutorial(context, reopenMediaFile);
});
@ -62,7 +61,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
@override
Widget build(BuildContext context) {
Color color = getMessageColorFromType(
final color = getMessageColorFromType(
widget.content,
context,
);
@ -75,7 +74,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
widget.message.mediaStored) {
return;
}
if (await received.existsMediaFile(widget.message.messageId, "png")) {
if (await received.existsMediaFile(widget.message.messageId, 'png')) {
await encryptAndSendMessageAsync(
null,
widget.contact.userId,
@ -93,7 +92,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
);
await twonlyDB.messagesDao.updateMessageByMessageId(
widget.message.messageId,
MessagesCompanion(openedAt: Value(null)),
const MessagesCompanion(openedAt: Value(null)),
);
}
},
@ -108,9 +107,9 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
initialMessage: widget.message);
}),
);
checkIfTutorialCanBeShown();
await checkIfTutorialCanBeShown();
} 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:twonly/src/database/twonly_database.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/better_text.dart';
import 'package:twonly/src/database/twonly_database.dart';
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 Message message;
@ -14,10 +14,10 @@ class ChatTextEntry extends StatelessWidget {
Widget build(BuildContext context) {
if (EmojiAnimation.supported(text)) {
return Container(
constraints: BoxConstraints(
constraints: const BoxConstraints(
maxWidth: 100,
),
padding: EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric(
vertical: 4,
horizontal: 10,
),
@ -28,10 +28,10 @@ class ChatTextEntry extends StatelessWidget {
constraints: BoxConstraints(
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(
color: getMessageColor(message),
borderRadius: BorderRadius.circular(12.0),
borderRadius: BorderRadius.circular(12),
),
child: BetterText(text: text),
);

View file

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

View file

@ -34,18 +34,18 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
backupServer,
);
Restart.restartApp(
await Restart.restartApp(
notificationTitle: 'Backup successfully recovered.',
notificationBody: 'Click here to open the app again',
);
} catch (e) {
// in case something was already written from the backup...
Log.error("$e");
Log.error('$e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
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) {
return Scaffold(
appBar: AppBar(
title: Text("twonly Safe ${context.lang.twonlySafeRecoverTitle}"),
title: Text('twonly Safe ${context.lang.twonlySafeRecoverTitle}'),
actions: [
IconButton(
onPressed: () {
showAlertDialog(
context,
"twonly Safe",
'twonly Safe',
context.lang.backupTwonlySafeLongDesc,
);
},
icon: FaIcon(FontAwesomeIcons.circleInfo),
icon: const FaIcon(FontAwesomeIcons.circleInfo),
iconSize: 18,
)
],
),
body: Padding(
padding: EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40),
padding:
const EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40),
child: ListView(
children: [
Text(
@ -95,7 +96,7 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
context.lang.registerUsernameDecoration,
),
),
SizedBox(height: 10),
const SizedBox(height: 10),
Stack(
children: [
TextField(
@ -130,30 +131,30 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
)
],
),
SizedBox(height: 30),
const SizedBox(height: 30),
Center(
child: OutlinedButton(
onPressed: () async {
backupServer = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return TwonlySafeServerView();
return const TwonlySafeServerView();
}));
setState(() {});
},
child: Text(context.lang.backupExpertSettings),
),
),
SizedBox(height: 10),
const SizedBox(height: 10),
Center(
child: FilledButton.icon(
onPressed: (!isLoading) ? _recoverTwonlySafe : null,
icon: isLoading
? SizedBox(
? const SizedBox(
height: 12,
width: 12,
child: CircularProgressIndicator(strokeWidth: 1),
)
: Icon(Icons.lock_clock_rounded),
: const Icon(Icons.lock_clock_rounded),
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';
class RefundCreditsView extends StatefulWidget {
const RefundCreditsView({super.key, required this.formattedBalance});
const RefundCreditsView({required this.formattedBalance, super.key});
final String formattedBalance;
@override
@ -19,7 +19,7 @@ class _RefundCreditsViewState extends State<RefundCreditsView> {
title: const Text('Refund Credits'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -32,13 +32,15 @@ class _RefundCreditsViewState extends State<RefundCreditsView> {
const SizedBox(height: 20), // Space between balance and options
ListTile(
title: Text("Create a Voucher"),
title: const Text('Create a Voucher'),
onTap: () async {
await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return VoucherView();
return const VoucherView();
}));
Navigator.pop(context, false);
if (context.mounted) {
Navigator.pop(context, false);
}
},
),
// ListTile(

View file

@ -38,7 +38,7 @@ class _ContactUsState extends State<ContactUsView> {
final messageOnSuccess = TextMessage()
..body = []
..userId = Int64(0);
..userId = Int64();
final uploadRequest = UploadRequest(
messagesOnSuccess: [messageOnSuccess],
@ -116,6 +116,7 @@ class _ContactUsState extends State<ContactUsView> {
'Debug Log: https://api.twonly.eu/api/download/$token';
}
} catch (e) {
if (!mounted) return '';
ScaffoldMessenger.of(context).showSnackBar(
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';
class SubmitMessage extends StatefulWidget {
const SubmitMessage({super.key, required this.fullMessage});
const SubmitMessage({required this.fullMessage, super.key});
final String fullMessage;
@ -22,14 +22,14 @@ class _ContactUsState extends State<SubmitMessage> {
}
Future<void> _submitFeedback() async {
final String feedback = _controller.text;
final feedback = _controller.text;
setState(() {
isLoading = true;
});
if (feedback.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please enter your message.')),
const SnackBar(content: Text('Please enter your message.')),
);
return;
}
@ -57,7 +57,7 @@ class _ContactUsState extends State<SubmitMessage> {
} else {
// Handle error response
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),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16),
child: ListView(
children: [
Text(
context.lang.contactUsLastWarning,
textAlign: TextAlign.center,
),
SizedBox(height: 10),
const SizedBox(height: 10),
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: context.lang.contactUsYourMessage,
border: OutlineInputBorder(),
border: const OutlineInputBorder(),
),
minLines: 5,
maxLines: 20,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 40),
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: (isLoading) ? null : _submitFeedback,
onPressed: isLoading ? null : _submitFeedback,
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';
class UrlListTitle extends StatelessWidget {
const UrlListTitle({
required this.title,
required this.url,
super.key,
this.leading,
this.subtitle,
});
final String? title;
final String url;
final String? subtitle;
final Widget? leading;
const UrlListTitle({
super.key,
required this.title,
required this.url,
this.leading,
this.subtitle,
});
@override
Widget build(BuildContext context) {
return ListTile(
@ -28,7 +27,7 @@ class UrlListTitle extends StatelessWidget {
onTap: () {
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 {
sticker = (await getStickerIndex()).where((x) => x.source != "").toList();
sticker = (await getStickerIndex()).where((x) => x.source != '').toList();
setState(() {});
}
@ -62,86 +61,86 @@ class _CreditsViewState extends State<CreditsView> {
),
body: ListView(
children: [
UrlListTitle(
title: "twonly Logo",
subtitle: "by Font Awesome (modified)",
url: "https://fontawesome.com/icons/link?f=classic&s=solid",
const UrlListTitle(
title: 'twonly Logo',
subtitle: 'by Font Awesome (modified)',
url: 'https://fontawesome.com/icons/link?f=classic&s=solid',
),
UrlListTitle(
title: "Most Icons",
subtitle: "by Font Awesome",
url: "https://github.com/FortAwesome/Font-Awesome",
const UrlListTitle(
title: 'Most Icons',
subtitle: 'by Font Awesome',
url: 'https://github.com/FortAwesome/Font-Awesome',
),
UrlListTitle(
title: "Animated Emoji",
subtitle: "CC BY 4.0",
url: "https://googlefonts.github.io/noto-emoji-animation/",
const UrlListTitle(
title: 'Animated Emoji',
subtitle: 'CC BY 4.0',
url: 'https://googlefonts.github.io/noto-emoji-animation/',
),
UrlListTitle(
title: "Avatar Icons",
url: "https://github.com/RoadTripMoustache/avatar_maker",
const UrlListTitle(
title: 'Avatar Icons',
url: 'https://github.com/RoadTripMoustache/avatar_maker',
),
const Divider(),
ListTile(
const ListTile(
title: Center(
child: Text(
"Animations",
style: const TextStyle(fontWeight: FontWeight.bold),
'Animations',
style: TextStyle(fontWeight: FontWeight.bold),
)),
),
UrlListTitle(
title: "selfie fast Animation",
subtitle: "Brandon Ambuila",
const UrlListTitle(
title: 'selfie fast Animation',
subtitle: 'Brandon Ambuila',
url:
"https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E",
'https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E',
),
UrlListTitle(
title: "Security status - Safe Animation",
subtitle: "Yogesh Pal",
const UrlListTitle(
title: 'Security status - Safe Animation',
subtitle: 'Yogesh Pal',
url:
"https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx",
'https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx',
),
UrlListTitle(
title: "send mail Animation",
subtitle: "jignesh gajjar",
url: "https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq",
const UrlListTitle(
title: 'send mail Animation',
subtitle: 'jignesh gajjar',
url: 'https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq',
),
UrlListTitle(
title: "Present for you Animation",
subtitle: "Tatsiana Melnikova",
const UrlListTitle(
title: 'Present for you Animation',
subtitle: 'Tatsiana Melnikova',
url:
"https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY",
'https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY',
),
UrlListTitle(
title: "Take a photo Animation",
subtitle: "Nguyễn Như Lân",
const UrlListTitle(
title: 'Take a photo Animation',
subtitle: 'Nguyễn Như Lân',
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",
subtitle: "Strezha",
subtitle: 'Strezha',
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(
title: "success-Animation",
subtitle: "Aman Awasthy",
const UrlListTitle(
title: 'success-Animation',
subtitle: 'Aman Awasthy',
url:
"https://lottiefiles.com/de/free-animation/success-tick-cuwjLHAR7g",
'https://lottiefiles.com/de/free-animation/success-tick-cuwjLHAR7g',
),
UrlListTitle(
title: "Failed-Animation",
subtitle: "Ahmed Shami أحمد شامي",
url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv",
const UrlListTitle(
title: 'Failed-Animation',
subtitle: 'Ahmed Shami أحمد شامي',
url: 'https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv',
),
const Divider(),
if (sticker.isNotEmpty)
ListTile(
const ListTile(
title: Center(
child: Text(
"Filters",
style: const TextStyle(fontWeight: FontWeight.bold),
'Filters',
style: TextStyle(fontWeight: FontWeight.bold),
)),
),
...sticker.map(
@ -150,9 +149,9 @@ class _CreditsViewState extends State<CreditsView> {
height: 50,
width: 50,
child: CachedNetworkImage(
imageUrl: "https://twonly.eu/${x.imageSrc}"),
imageUrl: 'https://twonly.eu/${x.imageSrc}'),
),
title: "",
title: '',
url: x.source,
),
),

View file

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

View file

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

View file

@ -43,7 +43,7 @@ class _PrivacyViewState extends State<PrivacyView> {
onTap: () async {
await Navigator.push(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:pie_menu/pie_menu.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/twonly_database.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';
class PrivacyViewBlockUsers extends StatefulWidget {
@ -18,7 +18,7 @@ class PrivacyViewBlockUsers extends StatefulWidget {
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
late Stream<List<Contact>> allUsers;
List<Contact> filteredUsers = [];
String filter = "";
String filter = '';
@override
void initState() {
@ -40,11 +40,12 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
body: PieCanvas(
theme: getPieCanvasTheme(context),
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(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField(
onChanged: (value) => setState(() {
filter = value;
@ -110,7 +111,7 @@ class UserList extends StatelessWidget {
restorationId: 'new_message_users_list',
itemCount: users.length,
itemBuilder: (BuildContext context, int i) {
Contact user = users[i];
final user = users[i];
return UserContextMenuBlocked(
contact: user,
child: ListTile(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -266,7 +266,7 @@ class _SubscriptionViewState extends State<SubscriptionView> {
PlanCard(
planId: 'Tester',
onTap: () async {
bool activate = await showAlertDialog(
final activate = await showAlertDialog(
context,
context.lang.testingAccountTitle,
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';
class TransactionView extends StatefulWidget {
const TransactionView(
{super.key, required this.transactions, required this.formattedBalance});
const TransactionView({
required this.transactions,
required this.formattedBalance,
super.key,
});
final List<Response_Transaction>? transactions;
final String formattedBalance;
@ -23,7 +26,7 @@ class _TransactionViewState extends State<TransactionView> {
body: ListView(
children: [
Padding(
padding: const EdgeInsets.all(32.0),
padding: const EdgeInsets.all(32),
child: Center(
child: Container(
decoration: BoxDecoration(
@ -51,10 +54,9 @@ class _TransactionViewState extends State<TransactionView> {
}
class TransactionCard extends StatefulWidget {
const TransactionCard({required this.transaction, super.key});
final Response_Transaction transaction;
const TransactionCard({super.key, required this.transaction});
@override
State<TransactionCard> createState() => _TransactionCardState();
}

View file

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

View file

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

View file

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

View file

@ -10,12 +10,20 @@ environment:
sdk: ^3.6.0
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
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_flutter: ^0.2.4
firebase_core: ^3.11.0
firebase_messaging: ^15.2.2
fixnum: ^1.1.1
flutter:
sdk: flutter
flutter_image_compress: ^2.4.0
@ -25,15 +33,17 @@ dependencies:
# flutter_secure_storage: ^10.0.0-beta.4
flutter_secure_storage:
path: ./dependencies/flutter_secure_storage/flutter_secure_storage
flutter_svg: ^2.0.17
flutter_zxing:
path: ./dependencies/flutter_zxing
# pie_menu: ^3.2.7
pie_menu:
path: ./dependencies/flutter-pie-menu
font_awesome_flutter: ^10.8.0
gal: ^2.3.1
get: ^4.7.2
hand_signature: ^3.0.3
hashlib: ^2.0.0
http: ^1.3.0
image: ^4.3.0
image_picker: ^1.1.2
intl: ^0.20.2
introduction_screen: ^3.1.14
json_annotation: ^4.9.0
@ -41,46 +51,35 @@ dependencies:
local_auth: ^2.3.0
logging: ^1.3.0
lottie: ^3.3.1
mutex: ^3.1.0
no_screenshot: ^0.3.1
package_info_plus: ^8.2.1
path: ^1.9.0
path_provider: ^2.1.5
permission_handler: ^12.0.0+1
photo_view: ^0.15.0
pie_menu:
path: ./dependencies/flutter-pie-menu
protobuf: ^4.0.0
cryptography_plus: ^2.7.0
provider: ^6.1.2
restart_app: ^1.3.2
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
photo_view: ^0.15.0
tutorial_coach_mark: ^1.3.0
background_downloader: ^9.2.2
hashlib: ^2.0.0
url_launcher: ^6.3.1
video_compress: ^3.1.4
video_player: ^2.9.5
video_thumbnail: ^0.5.6
device_info_plus: ^11.5.0
cryptography_flutter_plus: ^2.3.4
web_socket_channel: ^3.0.1
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:
sdk: flutter
build_runner: ^2.4.15
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
flutter_launcher_icons:

View file

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