mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
fixing all linter errors
This commit is contained in:
parent
575c0ca8ae
commit
c70413cf6a
81 changed files with 653 additions and 666 deletions
|
|
@ -14,6 +14,7 @@ analyzer:
|
|||
- "lib/src/model/protobuf/**"
|
||||
- "lib/src/model/protobuf/api/websocket/**"
|
||||
- "lib/generated/**"
|
||||
- "dependencies/**"
|
||||
- "test/drift/**"
|
||||
- "**.g.dart"
|
||||
|
||||
|
|
|
|||
10
lib/app.dart
10
lib/app.dart
|
|
@ -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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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))();
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// ignore_for_file: avoid_dynamic_calls
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class FilterText extends StatelessWidget {
|
|||
shadows: const [
|
||||
Shadow(
|
||||
color: Color.fromARGB(122, 0, 0, 0),
|
||||
blurRadius: 5.0,
|
||||
blurRadius: 5,
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
))
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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!')),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class NotificationView extends StatelessWidget {
|
|||
final pushData = await getPushData(
|
||||
user.userId,
|
||||
PushNotification(
|
||||
messageId: Int64(0),
|
||||
messageId: Int64(),
|
||||
kind: PushKind.testNotification,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class _PrivacyViewState extends State<PrivacyView> {
|
|||
onTap: () async {
|
||||
await Navigator.push(context,
|
||||
MaterialPageRoute(builder: (context) {
|
||||
return PrivacyViewBlockUsers();
|
||||
return const PrivacyViewBlockUsers();
|
||||
}));
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
|
|||
),
|
||||
onTap: toggleRenewalOption,
|
||||
trailing: Checkbox(
|
||||
value: autoRenewal!,
|
||||
value: autoRenewal,
|
||||
onChanged: (a) {
|
||||
toggleRenewalOption();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
51
pubspec.yaml
51
pubspec.yaml
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue