mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 16:28: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/**"
|
||||||
- "lib/src/model/protobuf/api/websocket/**"
|
- "lib/src/model/protobuf/api/websocket/**"
|
||||||
- "lib/generated/**"
|
- "lib/generated/**"
|
||||||
|
- "dependencies/**"
|
||||||
- "test/drift/**"
|
- "test/drift/**"
|
||||||
- "**.g.dart"
|
- "**.g.dart"
|
||||||
|
|
||||||
|
|
|
||||||
10
lib/app.dart
10
lib/app.dart
|
|
@ -106,7 +106,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
Locale('en', ''),
|
Locale('en', ''),
|
||||||
Locale('de', ''),
|
Locale('de', ''),
|
||||||
],
|
],
|
||||||
onGenerateTitle: (BuildContext context) => "twonly",
|
onGenerateTitle: (BuildContext context) => 'twonly',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
colorScheme: ColorScheme.fromSeed(
|
||||||
seedColor: const Color(0xFF57CC99),
|
seedColor: const Color(0xFF57CC99),
|
||||||
|
|
@ -132,8 +132,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
themeMode: context.watch<SettingsChangeProvider>().themeMode,
|
themeMode: context.watch<SettingsChangeProvider>().themeMode,
|
||||||
initialRoute: '/',
|
initialRoute: '/',
|
||||||
routes: {
|
routes: {
|
||||||
"/": (context) => AppMainWidget(initialPage: 1),
|
'/': (context) => const AppMainWidget(initialPage: 1),
|
||||||
"/chats": (context) => AppMainWidget(initialPage: 0)
|
'/chats': (context) => const AppMainWidget(initialPage: 0)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -143,8 +143,8 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
|
|
||||||
class AppMainWidget extends StatefulWidget {
|
class AppMainWidget extends StatefulWidget {
|
||||||
const AppMainWidget({
|
const AppMainWidget({
|
||||||
super.key,
|
|
||||||
required this.initialPage,
|
required this.initialPage,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
final int initialPage;
|
final int initialPage;
|
||||||
@override
|
@override
|
||||||
|
|
@ -182,7 +182,7 @@ class _AppMainWidgetState extends State<AppMainWidget> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
AppOutdated(),
|
const AppOutdated(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Future<void> updateContact(
|
Future<void> updateContact(
|
||||||
int userId, ContactsCompanion updatedValues) async {
|
int userId, ContactsCompanion updatedValues) async {
|
||||||
await ((update(contacts)..where((c) => c.userId.equals(userId)))
|
await (update(contacts)..where((c) => c.userId.equals(userId)))
|
||||||
.write(updatedValues));
|
.write(updatedValues);
|
||||||
if (updatedValues.blocked.present ||
|
if (updatedValues.blocked.present ||
|
||||||
updatedValues.displayName.present ||
|
updatedValues.displayName.present ||
|
||||||
updatedValues.nickName.present) {
|
updatedValues.nickName.present) {
|
||||||
|
|
@ -177,8 +177,9 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Stream<int?> watchContactsBlocked() {
|
Stream<int?> watchContactsBlocked() {
|
||||||
final count = contacts.userId.count();
|
final count = contacts.userId.count();
|
||||||
final query = selectOnly(contacts)..where(contacts.blocked.equals(true));
|
final query = selectOnly(contacts)
|
||||||
query.addColumns([count]);
|
..where(contacts.blocked.equals(true))
|
||||||
|
..addColumns([count]);
|
||||||
return query.map((row) => row.read(count)).watchSingle();
|
return query.map((row) => row.read(count)).watchSingle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,8 +187,8 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
final count = contacts.requested.count(distinct: true);
|
final count = contacts.requested.count(distinct: true);
|
||||||
final query = selectOnly(contacts)
|
final query = selectOnly(contacts)
|
||||||
..where(contacts.requested.equals(true) &
|
..where(contacts.requested.equals(true) &
|
||||||
contacts.accepted.equals(true).not());
|
contacts.accepted.equals(true).not())
|
||||||
query.addColumns([count]);
|
..addColumns([count]);
|
||||||
return query.map((row) => row.read(count)).watchSingle();
|
return query.map((row) => row.read(count)).watchSingle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,15 +205,13 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
u.lastMessageSend.isNotNull(),
|
u.lastMessageSend.isNotNull(),
|
||||||
))
|
))
|
||||||
.watchSingle()
|
.watchSingle()
|
||||||
.asyncMap((contact) {
|
.asyncMap(getFlameCounterFromContact);
|
||||||
return getFlameCounterFromContact(contact);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getContactDisplayName(Contact user) {
|
String getContactDisplayName(Contact user) {
|
||||||
String name = user.username;
|
var name = user.username;
|
||||||
if (user.nickName != null && user.nickName != "") {
|
if (user.nickName != null && user.nickName != '') {
|
||||||
name = user.nickName!;
|
name = user.nickName!;
|
||||||
} else if (user.displayName != null) {
|
} else if (user.displayName != null) {
|
||||||
name = user.displayName!;
|
name = user.displayName!;
|
||||||
|
|
@ -224,7 +223,7 @@ String getContactDisplayName(Contact user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String applyStrikethrough(String text) {
|
String applyStrikethrough(String text) {
|
||||||
return text.split('').map((char) => '$char\u0336').join('');
|
return text.split('').map((char) => '$char\u0336').join();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFlameCounterFromContact(Contact contact) {
|
int getFlameCounterFromContact(Contact contact) {
|
||||||
|
|
@ -233,7 +232,7 @@ int getFlameCounterFromContact(Contact contact) {
|
||||||
}
|
}
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final startOfToday = DateTime(now.year, now.month, now.day);
|
final startOfToday = DateTime(now.year, now.month, now.day);
|
||||||
final twoDaysAgo = startOfToday.subtract(Duration(days: 2));
|
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
|
||||||
if (contact.lastMessageSend!.isAfter(twoDaysAgo) &&
|
if (contact.lastMessageSend!.isAfter(twoDaysAgo) &&
|
||||||
contact.lastMessageReceived!.isAfter(twoDaysAgo)) {
|
contact.lastMessageReceived!.isAfter(twoDaysAgo)) {
|
||||||
return contact.flameCounter + 1;
|
return contact.flameCounter + 1;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class MediaDownloadsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
try {
|
try {
|
||||||
return await into(mediaDownloads).insert(values);
|
return await into(mediaDownloads).insert(values);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Error while inserting media upload: $e");
|
Log.error('Error while inserting media upload: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class MediaUploadsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
try {
|
try {
|
||||||
return await into(mediaUploads).insert(values);
|
return await into(mediaUploads).insert(values);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Error while inserting media upload: $e");
|
Log.error('Error while inserting media upload: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
try {
|
try {
|
||||||
return await into(messageRetransmissions).insert(message);
|
return await into(messageRetransmissions).insert(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Error while inserting message for retransmission: $e");
|
Log.error('Error while inserting message for retransmission: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +29,7 @@ class MessageRetransmissionDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
.go();
|
.go();
|
||||||
|
|
||||||
if (countDeleted > 0) {
|
if (countDeleted > 0) {
|
||||||
Log.info("Deleted $countDeleted faulty retransmissions");
|
Log.info('Deleted $countDeleted faulty retransmissions');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await (select(messageRetransmissions)
|
return (await (select(messageRetransmissions)
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
(t.openedAt.isNull() |
|
(t.openedAt.isNull() |
|
||||||
t.mediaStored.equals(true) |
|
t.mediaStored.equals(true) |
|
||||||
t.openedAt.isBiggerThanValue(
|
t.openedAt.isBiggerThanValue(
|
||||||
DateTime.now().subtract(Duration(days: 1)))))
|
DateTime.now().subtract(const Duration(days: 1)))))
|
||||||
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
|
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
@ -60,13 +60,13 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((t) =>
|
..where((t) =>
|
||||||
(t.openedAt.isSmallerThanValue(
|
(t.openedAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(Duration(days: 1)),
|
DateTime.now().subtract(const Duration(days: 1)),
|
||||||
) |
|
) |
|
||||||
(t.sendAt.isSmallerThanValue(
|
(t.sendAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(Duration(days: 1))) &
|
DateTime.now().subtract(const Duration(days: 1))) &
|
||||||
t.errorWhileSending.equals(true))) &
|
t.errorWhileSending.equals(true))) &
|
||||||
t.kind.equals(MessageKind.textMessage.name)))
|
t.kind.equals(MessageKind.textMessage.name)))
|
||||||
.write(MessagesCompanion(contentJson: Value(null)));
|
.write(const MessagesCompanion(contentJson: Value(null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleMediaFilesOlderThan7Days() {
|
Future<void> handleMediaFilesOlderThan7Days() {
|
||||||
|
|
@ -78,11 +78,11 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
t.messageOtherId.isNull() &
|
t.messageOtherId.isNull() &
|
||||||
(t.sendAt.isSmallerThanValue(
|
(t.sendAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
DateTime.now().subtract(
|
||||||
Duration(days: 8),
|
const Duration(days: 8),
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
))
|
))
|
||||||
.write(MessagesCompanion(errorWhileSending: Value(true)));
|
.write(const MessagesCompanion(errorWhileSending: Value(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Message>> getAllMessagesPendingDownloading() {
|
Future<List<Message>> getAllMessagesPendingDownloading() {
|
||||||
|
|
@ -104,7 +104,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
t.messageOtherId.isNull() &
|
t.messageOtherId.isNull() &
|
||||||
t.errorWhileSending.equals(false) &
|
t.errorWhileSending.equals(false) &
|
||||||
t.sendAt.isBiggerThanValue(
|
t.sendAt.isBiggerThanValue(
|
||||||
DateTime.now().subtract(Duration(minutes: 10)),
|
DateTime.now().subtract(const Duration(minutes: 10)),
|
||||||
)))
|
)))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
@ -142,10 +142,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetPendingDownloadState() {
|
Future<void> resetPendingDownloadState() {
|
||||||
// All media files in the downloading state are reseteded to the pending state
|
// All media files in the downloading state are reset to the pending state
|
||||||
// When the app is used in mobile network, they will not be downloaded at the start
|
// When the app is used in mobile network, they will not be downloaded at the start
|
||||||
// if they are not yet downloaded...
|
// if they are not yet downloaded...
|
||||||
final updates =
|
const updates =
|
||||||
MessagesCompanion(downloadState: Value(DownloadState.pending));
|
MessagesCompanion(downloadState: Value(DownloadState.pending));
|
||||||
return (update(messages)
|
return (update(messages)
|
||||||
..where((t) =>
|
..where((t) =>
|
||||||
|
|
@ -198,7 +198,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
return await into(messages).insert(message);
|
return await into(messages).insert(message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Error while inserting message: $e");
|
Log.error('Error while inserting message: $e');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +244,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Message>> getMessagesByMediaUploadId(int mediaUploadId) async {
|
Future<List<Message>> getMessagesByMediaUploadId(int mediaUploadId) async {
|
||||||
return await (select(messages)
|
return (select(messages)
|
||||||
..where((t) => t.mediaUploadId.equals(mediaUploadId)))
|
..where((t) => t.mediaUploadId.equals(mediaUploadId)))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,9 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
List<SignalContactPreKeysCompanion> preKeys) async {
|
List<SignalContactPreKeysCompanion> preKeys) async {
|
||||||
for (final preKey in preKeys) {
|
for (final preKey in preKeys) {
|
||||||
try {
|
try {
|
||||||
into(signalContactPreKeys).insert(preKey);
|
await into(signalContactPreKeys).insert(preKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("$e");
|
Log.error('$e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +90,7 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
await (delete(signalContactSignedPreKeys)
|
await (delete(signalContactSignedPreKeys)
|
||||||
..where((t) => (t.createdAt.isSmallerThanValue(
|
..where((t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
DateTime.now().subtract(
|
||||||
Duration(days: 25),
|
const Duration(days: 25),
|
||||||
),
|
),
|
||||||
))))
|
))))
|
||||||
.go();
|
.go();
|
||||||
|
|
@ -98,7 +98,7 @@ class SignalDao extends DatabaseAccessor<TwonlyDatabase> with _$SignalDaoMixin {
|
||||||
await (delete(twonlyDB.signalPreKeyStores)
|
await (delete(twonlyDB.signalPreKeyStores)
|
||||||
..where((t) => (t.createdAt.isSmallerThanValue(
|
..where((t) => (t.createdAt.isSmallerThanValue(
|
||||||
DateTime.now().subtract(
|
DateTime.now().subtract(
|
||||||
Duration(days: 40),
|
const Duration(days: 40),
|
||||||
),
|
),
|
||||||
))))
|
))))
|
||||||
.go();
|
.go();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
|
|
||||||
class ConnectSenderKeyStore extends SenderKeyStore {
|
class ConnectSenderKeyStore extends SenderKeyStore {
|
||||||
@override
|
@override
|
||||||
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
|
Future<SenderKeyRecord> loadSenderKey(SenderKeyName senderKeyName) async {
|
||||||
SignalSenderKeyStore? identity =
|
final identity = await (twonlyDB.select(twonlyDB.signalSenderKeyStores)
|
||||||
await (twonlyDB.select(twonlyDB.signalSenderKeyStores)
|
|
||||||
..where((t) => t.senderKeyName.equals(senderKeyName.serialize())))
|
..where((t) => t.senderKeyName.equals(senderKeyName.serialize())))
|
||||||
.getSingleOrNull();
|
.getSingleOrNull();
|
||||||
if (identity == null) {
|
if (identity == null) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/globals.dart';
|
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
|
|
||||||
class ConnectSessionStore extends SessionStore {
|
class ConnectSessionStore extends SessionStore {
|
||||||
|
|
@ -50,7 +50,7 @@ class ConnectSessionStore extends SessionStore {
|
||||||
if (dbSession.isEmpty) {
|
if (dbSession.isEmpty) {
|
||||||
return SessionRecord();
|
return SessionRecord();
|
||||||
}
|
}
|
||||||
Uint8List session = dbSession.first.sessionRecord;
|
final session = dbSession.first.sessionRecord;
|
||||||
return SessionRecord.fromSerialized(session);
|
return SessionRecord.fromSerialized(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:twonly/src/database/signal/connect_identitiy_key_store.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
|
import 'package:twonly/src/database/signal/connect_identity_key_store.dart';
|
||||||
import 'package:twonly/src/database/signal/connect_pre_key_store.dart';
|
import 'package:twonly/src/database/signal/connect_pre_key_store.dart';
|
||||||
import 'package:twonly/src/database/signal/connect_session_store.dart';
|
import 'package:twonly/src/database/signal/connect_session_store.dart';
|
||||||
import 'package:twonly/src/database/signal/connect_signed_pre_key_store.dart';
|
import 'package:twonly/src/database/signal/connect_signed_pre_key_store.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
|
||||||
|
|
||||||
class ConnectSignalProtocolStore implements SignalProtocolStore {
|
class ConnectSignalProtocolStore implements SignalProtocolStore {
|
||||||
ConnectSignalProtocolStore(
|
ConnectSignalProtocolStore(
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,25 @@ class Contacts extends Table {
|
||||||
TextColumn get nickName => text().nullable()();
|
TextColumn get nickName => text().nullable()();
|
||||||
TextColumn get avatarSvg => text().nullable()();
|
TextColumn get avatarSvg => text().nullable()();
|
||||||
|
|
||||||
IntColumn get myAvatarCounter => integer().withDefault(Constant(0))();
|
IntColumn get myAvatarCounter => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
BoolColumn get accepted => boolean().withDefault(Constant(false))();
|
BoolColumn get accepted => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get requested => boolean().withDefault(Constant(false))();
|
BoolColumn get requested => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get blocked => boolean().withDefault(Constant(false))();
|
BoolColumn get blocked => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get verified => boolean().withDefault(Constant(false))();
|
BoolColumn get verified => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get archived => boolean().withDefault(Constant(false))();
|
BoolColumn get archived => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get pinned => boolean().withDefault(Constant(false))();
|
BoolColumn get pinned => boolean().withDefault(const Constant(false))();
|
||||||
BoolColumn get deleted => boolean().withDefault(Constant(false))();
|
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
BoolColumn get alsoBestFriend => boolean().withDefault(Constant(false))();
|
BoolColumn get alsoBestFriend =>
|
||||||
|
boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
IntColumn get deleteMessagesAfterXMinutes =>
|
IntColumn get deleteMessagesAfterXMinutes =>
|
||||||
integer().withDefault(Constant(60 * 24))();
|
integer().withDefault(const Constant(60 * 24))();
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
IntColumn get totalMediaCounter => integer().withDefault(Constant(0))();
|
IntColumn get totalMediaCounter => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
DateTimeColumn get lastMessageSend => dateTime().nullable()();
|
DateTimeColumn get lastMessageSend => dateTime().nullable()();
|
||||||
DateTimeColumn get lastMessageReceived => dateTime().nullable()();
|
DateTimeColumn get lastMessageReceived => dateTime().nullable()();
|
||||||
|
|
@ -34,7 +35,7 @@ class Contacts extends Table {
|
||||||
DateTimeColumn get lastMessageExchange =>
|
DateTimeColumn get lastMessageExchange =>
|
||||||
dateTime().withDefault(currentDateAndTime)();
|
dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
IntColumn get flameCounter => integer().withDefault(Constant(0))();
|
IntColumn get flameCounter => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {userId};
|
Set<Column> get primaryKey => {userId};
|
||||||
|
|
|
||||||
|
|
@ -44,16 +44,18 @@ class Messages extends Table {
|
||||||
IntColumn get responseToMessageId => integer().nullable()();
|
IntColumn get responseToMessageId => integer().nullable()();
|
||||||
IntColumn get responseToOtherMessageId => integer().nullable()();
|
IntColumn get responseToOtherMessageId => integer().nullable()();
|
||||||
|
|
||||||
BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))();
|
BoolColumn get acknowledgeByUser =>
|
||||||
BoolColumn get mediaStored => boolean().withDefault(Constant(false))();
|
boolean().withDefault(const Constant(false))();
|
||||||
|
BoolColumn get mediaStored => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
IntColumn get downloadState => intEnum<DownloadState>()
|
IntColumn get downloadState => intEnum<DownloadState>()
|
||||||
.withDefault(Constant(DownloadState.downloaded.index))();
|
.withDefault(Constant(DownloadState.downloaded.index))();
|
||||||
|
|
||||||
BoolColumn get acknowledgeByServer =>
|
BoolColumn get acknowledgeByServer =>
|
||||||
boolean().withDefault(Constant(false))();
|
boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
BoolColumn get errorWhileSending => boolean().withDefault(Constant(false))();
|
BoolColumn get errorWhileSending =>
|
||||||
|
boolean().withDefault(const Constant(false))();
|
||||||
TextColumn get mediaRetransmissionState => textEnum<MediaRetransmitting>()
|
TextColumn get mediaRetransmissionState => textEnum<MediaRetransmitting>()
|
||||||
.withDefault(Constant(MediaRetransmitting.none.name))();
|
.withDefault(Constant(MediaRetransmitting.none.name))();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'signal_identity.g.dart';
|
part 'signal_identity.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
@ -9,13 +11,13 @@ class SignalIdentity {
|
||||||
required this.identityKeyPairU8List,
|
required this.identityKeyPairU8List,
|
||||||
required this.registrationId,
|
required this.registrationId,
|
||||||
});
|
});
|
||||||
|
factory SignalIdentity.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SignalIdentityFromJson(json);
|
||||||
|
|
||||||
final int registrationId;
|
final int registrationId;
|
||||||
|
|
||||||
@Uint8ListConverter()
|
@Uint8ListConverter()
|
||||||
final Uint8List identityKeyPairU8List;
|
final Uint8List identityKeyPairU8List;
|
||||||
factory SignalIdentity.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SignalIdentityFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$SignalIdentityToJson(this);
|
Map<String, dynamic> toJson() => _$SignalIdentityToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ class UserData {
|
||||||
required this.subscriptionPlan,
|
required this.subscriptionPlan,
|
||||||
required this.isDemoUser,
|
required this.isDemoUser,
|
||||||
});
|
});
|
||||||
|
factory UserData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UserDataFromJson(json);
|
||||||
|
|
||||||
final int userId;
|
final int userId;
|
||||||
|
|
||||||
|
|
@ -29,7 +31,7 @@ class UserData {
|
||||||
|
|
||||||
// --- SUBSCRIPTION DTA ---
|
// --- SUBSCRIPTION DTA ---
|
||||||
|
|
||||||
@JsonKey(defaultValue: "Preview")
|
@JsonKey(defaultValue: 'Preview')
|
||||||
String subscriptionPlan;
|
String subscriptionPlan;
|
||||||
DateTime? lastImageSend;
|
DateTime? lastImageSend;
|
||||||
int? todaysImageCounter;
|
int? todaysImageCounter;
|
||||||
|
|
@ -81,9 +83,6 @@ class UserData {
|
||||||
DateTime? nextTimeToShowBackupNotice;
|
DateTime? nextTimeToShowBackupNotice;
|
||||||
BackupServer? backupServer;
|
BackupServer? backupServer;
|
||||||
TwonlySafeBackup? twonlySafeBackup;
|
TwonlySafeBackup? twonlySafeBackup;
|
||||||
|
|
||||||
factory UserData.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$UserDataFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$UserDataToJson(this);
|
Map<String, dynamic> toJson() => _$UserDataToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,15 +94,14 @@ class TwonlySafeBackup {
|
||||||
required this.backupId,
|
required this.backupId,
|
||||||
required this.encryptionKey,
|
required this.encryptionKey,
|
||||||
});
|
});
|
||||||
|
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$TwonlySafeBackupFromJson(json);
|
||||||
|
|
||||||
int lastBackupSize = 0;
|
int lastBackupSize = 0;
|
||||||
LastBackupUploadState backupUploadState = LastBackupUploadState.none;
|
LastBackupUploadState backupUploadState = LastBackupUploadState.none;
|
||||||
DateTime? lastBackupDone;
|
DateTime? lastBackupDone;
|
||||||
List<int> backupId;
|
List<int> backupId;
|
||||||
List<int> encryptionKey;
|
List<int> encryptionKey;
|
||||||
|
|
||||||
factory TwonlySafeBackup.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$TwonlySafeBackupFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$TwonlySafeBackupToJson(this);
|
Map<String, dynamic> toJson() => _$TwonlySafeBackupToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,12 +112,11 @@ class BackupServer {
|
||||||
required this.retentionDays,
|
required this.retentionDays,
|
||||||
required this.maxBackupBytes,
|
required this.maxBackupBytes,
|
||||||
});
|
});
|
||||||
|
factory BackupServer.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$BackupServerFromJson(json);
|
||||||
|
|
||||||
String serverUrl;
|
String serverUrl;
|
||||||
int retentionDays;
|
int retentionDays;
|
||||||
int maxBackupBytes;
|
int maxBackupBytes;
|
||||||
|
|
||||||
factory BackupServer.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$BackupServerFromJson(json);
|
|
||||||
Map<String, dynamic> toJson() => _$BackupServerToJson(this);
|
Map<String, dynamic> toJson() => _$BackupServerToJson(this);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||||
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
String plan = "Preview";
|
String plan = 'Preview';
|
||||||
Future<void> updateConnectionState(bool update) async {
|
Future<void> updateConnectionState(bool update) async {
|
||||||
_isConnected = update;
|
_isConnected = update;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,13 @@ class ApiService {
|
||||||
|
|
||||||
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
|
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
|
||||||
IOWebSocketChannel? _channel;
|
IOWebSocketChannel? _channel;
|
||||||
|
// ignore: cancel_subscriptions
|
||||||
StreamSubscription<List<ConnectivityResult>>? connectivitySubscription;
|
StreamSubscription<List<ConnectivityResult>>? connectivitySubscription;
|
||||||
|
|
||||||
Future<bool> _connectTo(String apiUrl) async {
|
Future<bool> _connectTo(String apiUrl) async {
|
||||||
if (appIsOutdated) return false;
|
if (appIsOutdated) return false;
|
||||||
try {
|
try {
|
||||||
var channel = IOWebSocketChannel.connect(
|
final channel = IOWebSocketChannel.connect(
|
||||||
Uri.parse(apiUrl),
|
Uri.parse(apiUrl),
|
||||||
);
|
);
|
||||||
_channel = channel;
|
_channel = channel;
|
||||||
|
|
@ -195,11 +196,11 @@ class ApiService {
|
||||||
onClosed();
|
onClosed();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onData(dynamic msgBuffer) async {
|
Future<void> _onData(dynamic msgBuffer) async {
|
||||||
try {
|
try {
|
||||||
final msg = server.ServerToClient.fromBuffer(msgBuffer as Uint8List);
|
final msg = server.ServerToClient.fromBuffer(msgBuffer as Uint8List);
|
||||||
if (msg.v0.hasResponse()) {
|
if (msg.v0.hasResponse()) {
|
||||||
removeFromRetransmissionBuffer(msg.v0.seq);
|
await removeFromRetransmissionBuffer(msg.v0.seq);
|
||||||
messagesV0[msg.v0.seq] = msg;
|
messagesV0[msg.v0.seq] = msg;
|
||||||
} else {
|
} else {
|
||||||
await handleServerMessage(msg);
|
await handleServerMessage(msg);
|
||||||
|
|
@ -212,7 +213,7 @@ class ApiService {
|
||||||
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
||||||
final startTime = DateTime.now();
|
final startTime = DateTime.now();
|
||||||
|
|
||||||
final timeout = Duration(seconds: 20);
|
const timeout = Duration(seconds: 20);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (messagesV0[seq] != null) {
|
if (messagesV0[seq] != null) {
|
||||||
|
|
@ -393,7 +394,7 @@ class ApiService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var handshake = Handshake()
|
final handshake = Handshake()
|
||||||
..getauthchallenge = Handshake_GetAuthChallenge();
|
..getauthchallenge = Handshake_GetAuthChallenge();
|
||||||
final req = createClientToServerFromHandshake(handshake);
|
final req = createClientToServerFromHandshake(handshake);
|
||||||
|
|
||||||
|
|
@ -445,7 +446,7 @@ class ApiService {
|
||||||
|
|
||||||
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
final signedPreKey = (await signalStore.loadSignedPreKeys())[0];
|
||||||
|
|
||||||
var register = Handshake_Register()
|
final register = Handshake_Register()
|
||||||
..username = username
|
..username = username
|
||||||
..publicIdentityKey =
|
..publicIdentityKey =
|
||||||
(await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
|
(await signalStore.getIdentityKeyPair()).getPublicKey().serialize()
|
||||||
|
|
@ -614,7 +615,7 @@ class ApiService {
|
||||||
..userId = Int64(userId);
|
..userId = Int64(userId);
|
||||||
final appData = ApplicationData()..getsignedprekeybyuserid = get;
|
final appData = ApplicationData()..getsignedprekeybyuserid = get;
|
||||||
final req = createClientToServerFromApplicationData(appData);
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
Result res = await sendRequestSync(req, contactId: userId);
|
final res = await sendRequestSync(req, contactId: userId);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
final ok = res.value as server.Response_Ok;
|
final ok = res.value as server.Response_Ok;
|
||||||
if (ok.hasSignedprekey()) {
|
if (ok.hasSignedprekey()) {
|
||||||
|
|
@ -628,11 +629,11 @@ class ApiService {
|
||||||
final get = ApplicationData_GetPrekeysByUserId()..userId = Int64(userId);
|
final get = ApplicationData_GetPrekeysByUserId()..userId = Int64(userId);
|
||||||
final appData = ApplicationData()..getprekeysbyuserid = get;
|
final appData = ApplicationData()..getprekeysbyuserid = get;
|
||||||
final req = createClientToServerFromApplicationData(appData);
|
final req = createClientToServerFromApplicationData(appData);
|
||||||
Result res = await sendRequestSync(req, contactId: userId);
|
final res = await sendRequestSync(req, contactId: userId);
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
final ok = res.value as server.Response_Ok;
|
final ok = res.value as server.Response_Ok;
|
||||||
if (ok.hasUserdata()) {
|
if (ok.hasUserdata()) {
|
||||||
server.Response_UserData data = ok.userdata;
|
final data = ok.userdata;
|
||||||
if (data.hasSignedPrekey() &&
|
if (data.hasSignedPrekey() &&
|
||||||
data.hasSignedPrekeyId() &&
|
data.hasSignedPrekeyId() &&
|
||||||
data.hasSignedPrekeySignature()) {
|
data.hasSignedPrekeySignature()) {
|
||||||
|
|
@ -650,7 +651,7 @@ class ApiService {
|
||||||
|
|
||||||
Future<Result> sendTextMessage(
|
Future<Result> sendTextMessage(
|
||||||
int target, Uint8List msg, List<int>? pushData) async {
|
int target, Uint8List msg, List<int>? pushData) async {
|
||||||
var testMessage = ApplicationData_TextMessage()
|
final testMessage = ApplicationData_TextMessage()
|
||||||
..userId = Int64(target)
|
..userId = Int64(target)
|
||||||
..body = msg;
|
..body = msg;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ Future<void> initFileDownloader() async {
|
||||||
Future<bool> checkForFailedUploads() async {
|
Future<bool> checkForFailedUploads() async {
|
||||||
final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload();
|
final messages = await twonlyDB.messagesDao.getAllMessagesPendingUpload();
|
||||||
final mediaUploadIds = <int>[];
|
final mediaUploadIds = <int>[];
|
||||||
for (var message in messages) {
|
for (final message in messages) {
|
||||||
if (mediaUploadIds.contains(message.mediaUploadId)) {
|
if (mediaUploadIds.contains(message.mediaUploadId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -690,7 +690,7 @@ Future<void> handleUploadWhenAppGoesBackground() async {
|
||||||
Log.info('App goes into background. Enqueue uploads to the background.');
|
Log.info('App goes into background. Enqueue uploads to the background.');
|
||||||
final keys = currentUploadTasks.keys.toList();
|
final keys = currentUploadTasks.keys.toList();
|
||||||
for (final key in keys) {
|
for (final key in keys) {
|
||||||
enqueueUploadTask(key);
|
await enqueueUploadTask(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: avoid_dynamic_calls
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ Result asResult(server.ServerToClient? msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientToServer createClientToServerFromHandshake(Handshake handshake) {
|
ClientToServer createClientToServerFromHandshake(Handshake handshake) {
|
||||||
var v0 = client.V0()
|
final v0 = client.V0()
|
||||||
..seq = Int64(0)
|
..seq = Int64()
|
||||||
..handshake = handshake;
|
..handshake = handshake;
|
||||||
return ClientToServer()..v0 = v0;
|
return ClientToServer()..v0 = v0;
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ ClientToServer createClientToServerFromHandshake(Handshake handshake) {
|
||||||
ClientToServer createClientToServerFromApplicationData(
|
ClientToServer createClientToServerFromApplicationData(
|
||||||
ApplicationData applicationData) {
|
ApplicationData applicationData) {
|
||||||
final v0 = client.V0()
|
final v0 = client.V0()
|
||||||
..seq = Int64(0)
|
..seq = Int64()
|
||||||
..applicationdata = applicationData;
|
..applicationdata = applicationData;
|
||||||
return ClientToServer()..v0 = v0;
|
return ClientToServer()..v0 = v0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,20 +4,19 @@ import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
|
import 'package:twonly/src/model/json/message.dart' as my;
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart' as my;
|
|
||||||
|
|
||||||
Future<void> syncFlameCounters() async {
|
Future<void> syncFlameCounters() async {
|
||||||
var user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
|
||||||
List<Contact> contacts =
|
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||||
await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
|
||||||
if (contacts.isEmpty) return;
|
if (contacts.isEmpty) return;
|
||||||
int maxMessageCounter = contacts.map((x) => x.totalMediaCounter).max;
|
final maxMessageCounter = contacts.map((x) => x.totalMediaCounter).max;
|
||||||
Contact bestFriend =
|
final bestFriend =
|
||||||
contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
|
contacts.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
|
||||||
|
|
||||||
if (user.myBestFriendContactId != bestFriend.userId) {
|
if (user.myBestFriendContactId != bestFriend.userId) {
|
||||||
|
|
@ -27,13 +26,13 @@ Future<void> syncFlameCounters() async {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Contact contact in contacts) {
|
for (final contact in contacts) {
|
||||||
if (contact.lastFlameCounterChange == null) continue;
|
if (contact.lastFlameCounterChange == null) continue;
|
||||||
if (contact.lastFlameSync != null) {
|
if (contact.lastFlameSync != null) {
|
||||||
if (isToday(contact.lastFlameSync!)) continue;
|
if (isToday(contact.lastFlameSync!)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int flameCounter = getFlameCounterFromContact(contact) - 1;
|
final flameCounter = getFlameCounterFromContact(contact) - 1;
|
||||||
|
|
||||||
// only sync when flame counter is higher than three days
|
// only sync when flame counter is higher than three days
|
||||||
if (flameCounter < 1 && bestFriend.userId != contact.userId) continue;
|
if (flameCounter < 1 && bestFriend.userId != contact.userId) continue;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
@ -29,24 +28,18 @@ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
int id = 0;
|
int id = 0;
|
||||||
|
|
||||||
Future<void> setupPushNotification() async {
|
Future<void> setupPushNotification() async {
|
||||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
const initializationSettingsAndroid =
|
||||||
AndroidInitializationSettings("ic_launcher_foreground");
|
AndroidInitializationSettings('ic_launcher_foreground');
|
||||||
|
|
||||||
final List<DarwinNotificationCategory> darwinNotificationCategories =
|
final darwinNotificationCategories = <DarwinNotificationCategory>[];
|
||||||
<DarwinNotificationCategory>[];
|
|
||||||
|
|
||||||
/// Note: permissions aren't requested here just to demonstrate that can be
|
/// Note: permissions aren't requested here just to demonstrate that can be
|
||||||
/// done later
|
/// done later
|
||||||
final DarwinInitializationSettings initializationSettingsDarwin =
|
final initializationSettingsDarwin = DarwinInitializationSettings(
|
||||||
DarwinInitializationSettings(
|
|
||||||
requestAlertPermission: true,
|
|
||||||
requestBadgePermission: true,
|
|
||||||
requestSoundPermission: true,
|
|
||||||
requestProvisionalPermission: false,
|
|
||||||
notificationCategories: darwinNotificationCategories,
|
notificationCategories: darwinNotificationCategories,
|
||||||
);
|
);
|
||||||
|
|
||||||
final InitializationSettings initializationSettings = InitializationSettings(
|
final initializationSettings = InitializationSettings(
|
||||||
android: initializationSettingsAndroid,
|
android: initializationSettingsAndroid,
|
||||||
iOS: initializationSettingsDarwin,
|
iOS: initializationSettingsDarwin,
|
||||||
);
|
);
|
||||||
|
|
@ -65,23 +58,22 @@ Future<void> createPushAvatars() async {
|
||||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||||
|
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
if (contact.avatarSvg == null) return null;
|
if (contact.avatarSvg == null) return;
|
||||||
|
|
||||||
final PictureInfo pictureInfo =
|
final pictureInfo =
|
||||||
await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null);
|
await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null);
|
||||||
|
|
||||||
final ui.Image image = await pictureInfo.picture.toImage(300, 300);
|
final image = await pictureInfo.picture.toImage(300, 300);
|
||||||
|
|
||||||
final ByteData? byteData =
|
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||||
await image.toByteData(format: ui.ImageByteFormat.png);
|
final pngBytes = byteData!.buffer.asUint8List();
|
||||||
final Uint8List pngBytes = byteData!.buffer.asUint8List();
|
|
||||||
|
|
||||||
// Get the directory to save the image
|
// Get the directory to save the image
|
||||||
final directory = await getApplicationCacheDirectory();
|
final directory = await getApplicationCacheDirectory();
|
||||||
final avatarsDirectory = Directory('${directory.path}/avatars');
|
final avatarsDirectory = Directory('${directory.path}/avatars');
|
||||||
|
|
||||||
// Create the avatars directory if it does not exist
|
// Create the avatars directory if it does not exist
|
||||||
if (!await avatarsDirectory.exists()) {
|
if (!avatarsDirectory.existsSync()) {
|
||||||
await avatarsDirectory.create(recursive: true);
|
await avatarsDirectory.create(recursive: true);
|
||||||
}
|
}
|
||||||
final filePath = '${avatarsDirectory.path}/${contact.userId}.png';
|
final filePath = '${avatarsDirectory.path}/${contact.userId}.png';
|
||||||
|
|
|
||||||
|
|
@ -43,18 +43,17 @@ Future<Uint8List?> signalEncryptMessage(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint(
|
final tempSignedPreKeyPublic = Curve.decodePoint(
|
||||||
DjbECPublicKey(Uint8List.fromList(signedPreKey.signedPreKey))
|
DjbECPublicKey(Uint8List.fromList(signedPreKey.signedPreKey))
|
||||||
.serialize(),
|
.serialize(),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
|
||||||
final Uint8List? tempSignedPreKeySignature = Uint8List.fromList(
|
final tempSignedPreKeySignature = Uint8List.fromList(
|
||||||
signedPreKey.signedPreKeySignature,
|
signedPreKey.signedPreKeySignature,
|
||||||
);
|
);
|
||||||
|
|
||||||
final IdentityKey? tempIdentityKey =
|
final tempIdentityKey = await signalStore.getIdentity(address);
|
||||||
await signalStore.getIdentity(address);
|
|
||||||
if (tempIdentityKey != null) {
|
if (tempIdentityKey != null) {
|
||||||
final preKeyBundle = PreKeyBundle(
|
final preKeyBundle = PreKeyBundle(
|
||||||
target,
|
target,
|
||||||
|
|
@ -79,9 +78,9 @@ Future<Uint8List?> signalEncryptMessage(
|
||||||
|
|
||||||
final ciphertext = await session.encrypt(plaintextContent);
|
final ciphertext = await session.encrypt(plaintextContent);
|
||||||
|
|
||||||
final b = BytesBuilder();
|
final b = BytesBuilder()
|
||||||
b.add(ciphertext.serialize());
|
..add(ciphertext.serialize())
|
||||||
b.add(intToBytes(ciphertext.getType()));
|
..add(intToBytes(ciphertext.getType()));
|
||||||
|
|
||||||
return b.takeBytes();
|
return b.takeBytes();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||||
as server;
|
as server;
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class OtherPreKeys {
|
class OtherPreKeys {
|
||||||
OtherPreKeys({
|
OtherPreKeys({
|
||||||
|
|
@ -20,21 +22,22 @@ class OtherPreKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutex requestNewKeys = Mutex();
|
Mutex requestNewKeys = Mutex();
|
||||||
DateTime lastPreKeyRequest = DateTime.now().subtract(Duration(hours: 1));
|
DateTime lastPreKeyRequest = DateTime.now().subtract(const Duration(hours: 1));
|
||||||
DateTime lastSignedPreKeyRequest = DateTime.now().subtract(Duration(hours: 1));
|
DateTime lastSignedPreKeyRequest =
|
||||||
|
DateTime.now().subtract(const Duration(hours: 1));
|
||||||
|
|
||||||
Future<void> requestNewPrekeysForContact(int contactId) async {
|
Future<void> requestNewPrekeysForContact(int contactId) async {
|
||||||
if (lastPreKeyRequest
|
if (lastPreKeyRequest
|
||||||
.isAfter(DateTime.now().subtract(Duration(seconds: 60)))) {
|
.isAfter(DateTime.now().subtract(const Duration(seconds: 60)))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.info("Requesting new PREKEYS for $contactId");
|
Log.info('Requesting new PREKEYS for $contactId');
|
||||||
lastPreKeyRequest = DateTime.now();
|
lastPreKeyRequest = DateTime.now();
|
||||||
requestNewKeys.protect(() async {
|
await requestNewKeys.protect(() async {
|
||||||
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
final otherKeys = await apiService.getPreKeysByUserId(contactId);
|
||||||
if (otherKeys != null) {
|
if (otherKeys != null) {
|
||||||
Log.info(
|
Log.info(
|
||||||
"got fresh ${otherKeys.preKeys.length} pre keys from other $contactId!");
|
'got fresh ${otherKeys.preKeys.length} pre keys from other $contactId!');
|
||||||
final preKeys = otherKeys.preKeys
|
final preKeys = otherKeys.preKeys
|
||||||
.map(
|
.map(
|
||||||
(preKey) => SignalContactPreKeysCompanion(
|
(preKey) => SignalContactPreKeysCompanion(
|
||||||
|
|
@ -46,30 +49,30 @@ Future<void> requestNewPrekeysForContact(int contactId) async {
|
||||||
.toList();
|
.toList();
|
||||||
await twonlyDB.signalDao.insertPreKeys(preKeys);
|
await twonlyDB.signalDao.insertPreKeys(preKeys);
|
||||||
} else {
|
} else {
|
||||||
Log.error("could not load new pre keys for user $contactId");
|
Log.error('could not load new pre keys for user $contactId');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SignalContactPreKey?> getPreKeyByContactId(int contactId) async {
|
Future<SignalContactPreKey?> getPreKeyByContactId(int contactId) async {
|
||||||
int count = await twonlyDB.signalDao.countPreKeysByContactId(contactId);
|
final count = await twonlyDB.signalDao.countPreKeysByContactId(contactId);
|
||||||
if (count < 10) {
|
if (count < 10) {
|
||||||
requestNewPrekeysForContact(contactId);
|
unawaited(requestNewPrekeysForContact(contactId));
|
||||||
}
|
}
|
||||||
return twonlyDB.signalDao.popPreKeyByContactId(contactId);
|
return twonlyDB.signalDao.popPreKeyByContactId(contactId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> requestNewSignedPreKeyForContact(int contactId) async {
|
Future<void> requestNewSignedPreKeyForContact(int contactId) async {
|
||||||
if (lastSignedPreKeyRequest
|
if (lastSignedPreKeyRequest
|
||||||
.isAfter(DateTime.now().subtract(Duration(seconds: 60)))) {
|
.isAfter(DateTime.now().subtract(const Duration(seconds: 60)))) {
|
||||||
Log.info("last signed pre request was 60s before");
|
Log.info('last signed pre request was 60s before');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastSignedPreKeyRequest = DateTime.now();
|
lastSignedPreKeyRequest = DateTime.now();
|
||||||
await requestNewKeys.protect(() async {
|
await requestNewKeys.protect(() async {
|
||||||
final signedPreKey = await apiService.getSignedKeyByUserId(contactId);
|
final signedPreKey = await apiService.getSignedKeyByUserId(contactId);
|
||||||
if (signedPreKey != null) {
|
if (signedPreKey != null) {
|
||||||
Log.info("got fresh signed pre keys from other $contactId!");
|
Log.info('got fresh signed pre keys from other $contactId!');
|
||||||
await twonlyDB.signalDao.insertOrUpdateSignedPreKeyByContactId(
|
await twonlyDB.signalDao.insertOrUpdateSignedPreKeyByContactId(
|
||||||
SignalContactSignedPreKeysCompanion(
|
SignalContactSignedPreKeysCompanion(
|
||||||
contactId: Value(contactId),
|
contactId: Value(contactId),
|
||||||
|
|
@ -79,7 +82,7 @@ Future<void> requestNewSignedPreKeyForContact(int contactId) async {
|
||||||
signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()),
|
signedPreKeyId: Value(signedPreKey.signedPrekeyId.toInt()),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
Log.error("could not load new signed pre key for user $contactId");
|
Log.error('could not load new signed pre key for user $contactId');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -87,19 +90,20 @@ Future<void> requestNewSignedPreKeyForContact(int contactId) async {
|
||||||
Future<SignalContactSignedPreKey?> getSignedPreKeyByContactId(
|
Future<SignalContactSignedPreKey?> getSignedPreKeyByContactId(
|
||||||
int contactId,
|
int contactId,
|
||||||
) async {
|
) async {
|
||||||
SignalContactSignedPreKey? signedPreKey =
|
final signedPreKey =
|
||||||
await twonlyDB.signalDao.getSignedPreKeyByContactId(contactId);
|
await twonlyDB.signalDao.getSignedPreKeyByContactId(contactId);
|
||||||
|
|
||||||
if (signedPreKey != null) {
|
if (signedPreKey != null) {
|
||||||
DateTime fortyEightHoursAgo = DateTime.now().subtract(Duration(hours: 48));
|
final fortyEightHoursAgo =
|
||||||
bool isOlderThan48Hours =
|
DateTime.now().subtract(const Duration(hours: 48));
|
||||||
(signedPreKey.createdAt).isBefore(fortyEightHoursAgo);
|
final isOlderThan48Hours =
|
||||||
|
signedPreKey.createdAt.isBefore(fortyEightHoursAgo);
|
||||||
if (isOlderThan48Hours) {
|
if (isOlderThan48Hours) {
|
||||||
requestNewSignedPreKeyForContact(contactId);
|
unawaited(requestNewSignedPreKeyForContact(contactId));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
requestNewSignedPreKeyForContact(contactId);
|
unawaited(requestNewSignedPreKeyForContact(contactId));
|
||||||
Log.error("Contact $contactId does not have a signed pre key!");
|
Log.error('Contact $contactId does not have a signed pre key!');
|
||||||
}
|
}
|
||||||
return signedPreKey;
|
return signedPreKey;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
||||||
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
|
||||||
import 'package:twonly/src/services/signal/consts.signal.dart';
|
import 'package:twonly/src/services/signal/consts.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
Future<bool> createNewSignalSession(Response_UserData userData) async {
|
Future<bool> createNewSignalSession(Response_UserData userData) async {
|
||||||
SignalProtocolStore? signalStore = await getSignalStore();
|
final SignalProtocolStore? signalStore = await getSignalStore();
|
||||||
|
|
||||||
if (signalStore == null) {
|
if (signalStore == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalProtocolAddress targetAddress = SignalProtocolAddress(
|
final targetAddress = SignalProtocolAddress(
|
||||||
userData.userId.toString(),
|
userData.userId.toString(),
|
||||||
defaultDeviceId,
|
defaultDeviceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
SessionBuilder sessionBuilder = SessionBuilder.fromSignalStore(
|
final sessionBuilder = SessionBuilder.fromSignalStore(
|
||||||
signalStore,
|
signalStore,
|
||||||
targetAddress,
|
targetAddress,
|
||||||
);
|
);
|
||||||
|
|
@ -38,18 +36,18 @@ Future<bool> createNewSignalSession(Response_UserData userData) async {
|
||||||
tempPreKeyId = userData.prekeys.first.id.toInt();
|
tempPreKeyId = userData.prekeys.first.id.toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
int tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
final tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
||||||
|
|
||||||
ECPublicKey? tempSignedPreKeyPublic = Curve.decodePoint(
|
final tempSignedPreKeyPublic = Curve.decodePoint(
|
||||||
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(),
|
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
|
||||||
Uint8List? tempSignedPreKeySignature = Uint8List.fromList(
|
final tempSignedPreKeySignature = Uint8List.fromList(
|
||||||
userData.signedPrekeySignature,
|
userData.signedPrekeySignature,
|
||||||
);
|
);
|
||||||
|
|
||||||
IdentityKey tempIdentityKey = IdentityKey(
|
final tempIdentityKey = IdentityKey(
|
||||||
Curve.decodePoint(
|
Curve.decodePoint(
|
||||||
DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey))
|
DjbECPublicKey(Uint8List.fromList(userData.publicIdentityKey))
|
||||||
.serialize(),
|
.serialize(),
|
||||||
|
|
@ -57,7 +55,7 @@ Future<bool> createNewSignalSession(Response_UserData userData) async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
PreKeyBundle preKeyBundle = PreKeyBundle(
|
final preKeyBundle = PreKeyBundle(
|
||||||
userData.userId.toInt(),
|
userData.userId.toInt(),
|
||||||
defaultDeviceId,
|
defaultDeviceId,
|
||||||
tempPreKeyId,
|
tempPreKeyId,
|
||||||
|
|
@ -72,32 +70,32 @@ Future<bool> createNewSignalSession(Response_UserData userData) async {
|
||||||
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("could not process pre key bundle: $e");
|
Log.error('could not process pre key bundle: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteSessionWithTarget(int target) async {
|
Future<void> deleteSessionWithTarget(int target) async {
|
||||||
ConnectSignalProtocolStore? signalStore = await getSignalStore();
|
final signalStore = await getSignalStore();
|
||||||
if (signalStore == null) return;
|
if (signalStore == null) return;
|
||||||
final address = SignalProtocolAddress(target.toString(), defaultDeviceId);
|
final address = SignalProtocolAddress(target.toString(), defaultDeviceId);
|
||||||
await signalStore.sessionStore.deleteSession(address);
|
await signalStore.sessionStore.deleteSession(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Fingerprint?> generateSessionFingerPrint(int target) async {
|
Future<Fingerprint?> generateSessionFingerPrint(int target) async {
|
||||||
ConnectSignalProtocolStore? signalStore = await getSignalStore();
|
final signalStore = await getSignalStore();
|
||||||
UserData? user = await getUser();
|
final user = await getUser();
|
||||||
if (signalStore == null || user == null) return null;
|
if (signalStore == null || user == null) return null;
|
||||||
try {
|
try {
|
||||||
IdentityKey? targetIdentity = await signalStore
|
final targetIdentity = await signalStore
|
||||||
.getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId));
|
.getIdentity(SignalProtocolAddress(target.toString(), defaultDeviceId));
|
||||||
if (targetIdentity != null) {
|
if (targetIdentity != null) {
|
||||||
final generator = NumericFingerprintGenerator(5200);
|
final generator = NumericFingerprintGenerator(5200);
|
||||||
final localFingerprint = generator.createFor(
|
final localFingerprint = generator.createFor(
|
||||||
1,
|
1,
|
||||||
Uint8List.fromList([user.userId.toInt()]),
|
Uint8List.fromList([user.userId]),
|
||||||
(await signalStore.getIdentityKeyPair()).getPublicKey(),
|
(await signalStore.getIdentityKeyPair()).getPublicKey(),
|
||||||
Uint8List.fromList([target.toInt()]),
|
Uint8List.fromList([target]),
|
||||||
targetIdentity,
|
targetIdentity,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/src/model/json/signal_identity.dart';
|
|
||||||
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart';
|
||||||
|
import 'package:twonly/src/model/json/signal_identity.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||||
|
|
||||||
Future<ConnectSignalProtocolStore?> getSignalStore() async {
|
Future<ConnectSignalProtocolStore?> getSignalStore() async {
|
||||||
return await getSignalStoreFromIdentity((await getSignalIdentity())!);
|
return getSignalStoreFromIdentity((await getSignalIdentity())!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
Future<ConnectSignalProtocolStore> getSignalStoreFromIdentity(
|
||||||
SignalIdentity signalIdentity) async {
|
SignalIdentity signalIdentity) async {
|
||||||
final IdentityKeyPair identityKeyPair =
|
final identityKeyPair =
|
||||||
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
IdentityKeyPair.fromSerialized(signalIdentity.identityKeyPairU8List);
|
||||||
|
|
||||||
return ConnectSignalProtocolStore(
|
return ConnectSignalProtocolStore(
|
||||||
identityKeyPair, signalIdentity.registrationId.toInt());
|
identityKeyPair,
|
||||||
|
signalIdentity.registrationId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:hashlib/hashlib.dart';
|
import 'package:hashlib/hashlib.dart';
|
||||||
|
|
@ -21,7 +22,7 @@ Future<void> enableTwonlySafe(String password) async {
|
||||||
);
|
);
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
performTwonlySafeBackup(force: true);
|
unawaited(performTwonlySafeBackup(force: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> disableTwonlySafe() async {
|
Future<void> disableTwonlySafe() async {
|
||||||
|
|
@ -35,9 +36,9 @@ Future<void> disableTwonlySafe() async {
|
||||||
// Add any other headers if required
|
// Add any other headers if required
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Log.info("Download deleted with: ${response.statusCode}");
|
Log.info('Download deleted with: ${response.statusCode}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error("Could not connect to the server.");
|
Log.error('Could not connect to the server.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
|
|
@ -50,21 +51,18 @@ Future<(Uint8List, Uint8List)> getMasterKey(
|
||||||
String password,
|
String password,
|
||||||
String username,
|
String username,
|
||||||
) async {
|
) async {
|
||||||
List<int> passwordBytes = utf8.encode(password);
|
final List<int> passwordBytes = utf8.encode(password);
|
||||||
List<int> saltBytes = utf8.encode(username);
|
final List<int> saltBytes = utf8.encode(username);
|
||||||
|
|
||||||
// Values are derived from the Threema Whitepaper
|
// Values are derived from the Threema Whitepaper
|
||||||
// https://threema.com/assets/documents/cryptography_whitepaper.pdf
|
// https://threema.com/assets/documents/cryptography_whitepaper.pdf
|
||||||
|
|
||||||
final scrypt = Scrypt(
|
final scrypt = Scrypt(
|
||||||
cost: 65536,
|
cost: 65536,
|
||||||
blockSize: 8,
|
|
||||||
parallelism: 1,
|
|
||||||
derivedKeyLength: 64,
|
|
||||||
salt: saltBytes,
|
salt: saltBytes,
|
||||||
);
|
);
|
||||||
|
|
||||||
final key = (scrypt.convert(passwordBytes)).bytes;
|
final key = scrypt.convert(passwordBytes).bytes;
|
||||||
return (key.sublist(0, 32), key.sublist(32, 64));
|
return (key.sublist(0, 32), key.sublist(32, 64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,13 +79,13 @@ Future<String?> getTwonlySafeBackupUrlFromServer(
|
||||||
List<int> backupId,
|
List<int> backupId,
|
||||||
BackupServer? backupServer,
|
BackupServer? backupServer,
|
||||||
) async {
|
) async {
|
||||||
String backupServerUrl = "https://safe.twonly.eu/";
|
var backupServerUrl = 'https://safe.twonly.eu/';
|
||||||
|
|
||||||
if (backupServer != null) {
|
if (backupServer != null) {
|
||||||
backupServerUrl = backupServer.serverUrl;
|
backupServerUrl = backupServer.serverUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
String backupIdHex = uint8ListToHex(backupId).toLowerCase();
|
final backupIdHex = uint8ListToHex(backupId).toLowerCase();
|
||||||
|
|
||||||
return "${backupServerUrl}backups/$backupIdHex";
|
return '${backupServerUrl}backups/$backupIdHex';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: parameter_assignments
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
|
@ -31,8 +33,8 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final DateTime? lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
|
final lastUpdateTime = user.twonlySafeBackup!.lastBackupDone;
|
||||||
if (!force && lastUpdateTime != null) {
|
if (force != true && lastUpdateTime != null) {
|
||||||
if (lastUpdateTime
|
if (lastUpdateTime
|
||||||
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
|
.isAfter(DateTime.now().subtract(const Duration(days: 1)))) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -179,8 +181,6 @@ Future<void> performTwonlySafeBackup({bool force = false}) async {
|
||||||
file: encryptedBackupBytesFile,
|
file: encryptedBackupBytesFile,
|
||||||
httpRequestMethod: 'PUT',
|
httpRequestMethod: 'PUT',
|
||||||
url: (await getTwonlySafeBackupUrl())!,
|
url: (await getTwonlySafeBackupUrl())!,
|
||||||
// requiresWiFi: true,
|
|
||||||
priority: 5,
|
|
||||||
post: 'binary',
|
post: 'binary',
|
||||||
retries: 2,
|
retries: 2,
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ Future<String> loadLogFile() async {
|
||||||
final directory = await getApplicationSupportDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
||||||
if (await logFile.exists()) {
|
if (logFile.existsSync()) {
|
||||||
return await logFile.readAsString();
|
return logFile.readAsString();
|
||||||
} else {
|
} else {
|
||||||
return 'Log file does not exist.';
|
return 'Log file does not exist.';
|
||||||
}
|
}
|
||||||
|
|
@ -51,7 +51,7 @@ Future<void> _writeLogToFile(LogRecord record) async {
|
||||||
final logMessage =
|
final logMessage =
|
||||||
'${DateTime.now().toString().split(".")[0]} ${record.level.name} [twonly] ${record.loggerName} > ${record.message}\n';
|
'${DateTime.now().toString().split(".")[0]} ${record.level.name} [twonly] ${record.loggerName} > ${record.message}\n';
|
||||||
|
|
||||||
writeToLogGuard.protect(() async {
|
await writeToLogGuard.protect(() async {
|
||||||
// Append the log message to the file
|
// Append the log message to the file
|
||||||
await logFile.writeAsString(logMessage, mode: FileMode.append);
|
await logFile.writeAsString(logMessage, mode: FileMode.append);
|
||||||
});
|
});
|
||||||
|
|
@ -61,7 +61,7 @@ Future<bool> deleteLogFile() async {
|
||||||
final directory = await getApplicationSupportDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
||||||
if (await logFile.exists()) {
|
if (logFile.existsSync()) {
|
||||||
await logFile.delete();
|
await logFile.delete();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -69,18 +69,18 @@ Future<bool> deleteLogFile() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getCallerSourceCodeFilename() {
|
String _getCallerSourceCodeFilename() {
|
||||||
StackTrace stackTrace = StackTrace.current;
|
final stackTrace = StackTrace.current;
|
||||||
String stackTraceString = stackTrace.toString();
|
final stackTraceString = stackTrace.toString();
|
||||||
String fileName = "";
|
var fileName = '';
|
||||||
String lineNumber = "";
|
var lineNumber = '';
|
||||||
List<String> stackLines = stackTraceString.split('\n');
|
final stackLines = stackTraceString.split('\n');
|
||||||
if (stackLines.length > 2) {
|
if (stackLines.length > 2) {
|
||||||
String callerInfo = stackLines[2];
|
final callerInfo = stackLines[2];
|
||||||
List<String> parts = callerInfo.split('/');
|
final parts = callerInfo.split('/');
|
||||||
fileName = parts.last.split(':').first; // Extract the file name
|
fileName = parts.last.split(':').first; // Extract the file name
|
||||||
lineNumber = parts.last.split(':')[1]; // Extract the line number
|
lineNumber = parts.last.split(':')[1]; // Extract the line number
|
||||||
} else {
|
} else {
|
||||||
String firstLine = stackTraceString.split('\n')[0];
|
final firstLine = stackTraceString.split('\n')[0];
|
||||||
fileName =
|
fileName =
|
||||||
firstLine.split('/').last.split(':').first; // Extract the file name
|
firstLine.split('/').last.split(':').first; // Extract the file name
|
||||||
lineNumber = firstLine.split(':')[1]; // Extract the line number
|
lineNumber = firstLine.split(':')[1]; // Extract the line number
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
// ignore_for_file: avoid_dynamic_calls
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
class PermissionHandlerView extends StatefulWidget {
|
class PermissionHandlerView extends StatefulWidget {
|
||||||
const PermissionHandlerView({super.key, required this.onSuccess});
|
const PermissionHandlerView({required this.onSuccess, super.key});
|
||||||
|
|
||||||
final Function onSuccess;
|
final Function onSuccess;
|
||||||
|
|
||||||
|
|
@ -25,7 +27,7 @@ Future<bool> checkPermissions() async {
|
||||||
class PermissionHandlerViewState extends State<PermissionHandlerView> {
|
class PermissionHandlerViewState extends State<PermissionHandlerView> {
|
||||||
Future<Map<Permission, PermissionStatus>> permissionServices() async {
|
Future<Map<Permission, PermissionStatus>> permissionServices() async {
|
||||||
// try {
|
// try {
|
||||||
Map<Permission, PermissionStatus> statuses = await [
|
final statuses = await [
|
||||||
Permission.camera,
|
Permission.camera,
|
||||||
// Permission.microphone,
|
// Permission.microphone,
|
||||||
Permission.notification
|
Permission.notification
|
||||||
|
|
@ -57,18 +59,17 @@ class PermissionHandlerViewState extends State<PermissionHandlerView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(100),
|
padding: const EdgeInsets.all(100),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const Text(
|
||||||
"twonly needs access to the camera and microphone.",
|
'twonly needs access to the camera and microphone.',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
label: Text("Request permissions"),
|
label: const Text('Request permissions'),
|
||||||
icon: const Icon(Icons.perm_camera_mic),
|
icon: const Icon(Icons.perm_camera_mic),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/media_upload.dart';
|
||||||
import 'package:twonly/src/services/thumbnail.service.dart';
|
import 'package:twonly/src/services/thumbnail.service.dart';
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
class SaveToGalleryButton extends StatefulWidget {
|
class SaveToGalleryButton extends StatefulWidget {
|
||||||
|
const SaveToGalleryButton({
|
||||||
|
required this.getMergedImage,
|
||||||
|
required this.isLoading,
|
||||||
|
required this.displayButtonLabel,
|
||||||
|
super.key,
|
||||||
|
this.mediaUploadId,
|
||||||
|
this.videoFilePath,
|
||||||
|
});
|
||||||
final Future<Uint8List?> Function() getMergedImage;
|
final Future<Uint8List?> Function() getMergedImage;
|
||||||
final bool displayButtonLabel;
|
final bool displayButtonLabel;
|
||||||
final File? videoFilePath;
|
final File? videoFilePath;
|
||||||
final int? mediaUploadId;
|
final int? mediaUploadId;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
||||||
const SaveToGalleryButton({
|
|
||||||
super.key,
|
|
||||||
required this.getMergedImage,
|
|
||||||
required this.isLoading,
|
|
||||||
required this.displayButtonLabel,
|
|
||||||
this.mediaUploadId,
|
|
||||||
this.videoFilePath,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SaveToGalleryButton> createState() => SaveToGalleryButtonState();
|
State<SaveToGalleryButton> createState() => SaveToGalleryButtonState();
|
||||||
}
|
}
|
||||||
|
|
@ -53,33 +53,33 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
});
|
});
|
||||||
|
|
||||||
String? res;
|
String? res;
|
||||||
String memoryPath = await getMediaBaseFilePath("memories");
|
var memoryPath = await getMediaBaseFilePath('memories');
|
||||||
|
|
||||||
if (widget.mediaUploadId != null) {
|
if (widget.mediaUploadId != null) {
|
||||||
memoryPath = join(memoryPath, "${widget.mediaUploadId!}");
|
memoryPath = join(memoryPath, '${widget.mediaUploadId!}');
|
||||||
} else {
|
} else {
|
||||||
final Random random = Random();
|
final random = Random();
|
||||||
String token = uint8ListToHex(
|
final token = uint8ListToHex(
|
||||||
List<int>.generate(32, (i) => random.nextInt(256)));
|
List<int>.generate(32, (i) => random.nextInt(256)));
|
||||||
memoryPath = join(memoryPath, token);
|
memoryPath = join(memoryPath, token);
|
||||||
}
|
}
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
bool storeToGallery = user.storeMediaFilesInGallery;
|
final storeToGallery = user.storeMediaFilesInGallery;
|
||||||
|
|
||||||
if (widget.videoFilePath != null) {
|
if (widget.videoFilePath != null) {
|
||||||
memoryPath += ".mp4";
|
memoryPath += '.mp4';
|
||||||
await File(widget.videoFilePath!.path).copy(memoryPath);
|
await File(widget.videoFilePath!.path).copy(memoryPath);
|
||||||
createThumbnailsForVideo(File(memoryPath));
|
unawaited(createThumbnailsForVideo(File(memoryPath)));
|
||||||
if (storeToGallery) {
|
if (storeToGallery) {
|
||||||
res = await saveVideoToGallery(widget.videoFilePath!.path);
|
res = await saveVideoToGallery(widget.videoFilePath!.path);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
memoryPath += ".png";
|
memoryPath += '.png';
|
||||||
Uint8List? imageBytes = await widget.getMergedImage();
|
final imageBytes = await widget.getMergedImage();
|
||||||
if (imageBytes == null || !mounted) return;
|
if (imageBytes == null || !mounted) return;
|
||||||
await File(memoryPath).writeAsBytes(imageBytes);
|
await File(memoryPath).writeAsBytes(imageBytes);
|
||||||
createThumbnailsForImage(File(memoryPath));
|
unawaited(createThumbnailsForImage(File(memoryPath)));
|
||||||
if (storeToGallery) {
|
if (storeToGallery) {
|
||||||
res = await saveImageToGallery(imageBytes);
|
res = await saveImageToGallery(imageBytes);
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +92,7 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(res),
|
content: Text(res),
|
||||||
duration: Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -102,15 +102,16 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
(_imageSaving || widget.isLoading)
|
if (_imageSaving || widget.isLoading)
|
||||||
? SizedBox(
|
const SizedBox(
|
||||||
width: 12,
|
width: 12,
|
||||||
height: 12,
|
height: 12,
|
||||||
child: CircularProgressIndicator(strokeWidth: 1))
|
child: CircularProgressIndicator(strokeWidth: 1))
|
||||||
: _imageSaved
|
else
|
||||||
? Icon(Icons.check)
|
_imageSaved
|
||||||
: FaIcon(FontAwesomeIcons.floppyDisk),
|
? const Icon(Icons.check)
|
||||||
if (widget.displayButtonLabel) SizedBox(width: 10),
|
: const FaIcon(FontAwesomeIcons.floppyDisk),
|
||||||
|
if (widget.displayButtonLabel) const SizedBox(width: 10),
|
||||||
if (widget.displayButtonLabel)
|
if (widget.displayButtonLabel)
|
||||||
Text(_imageSaved
|
Text(_imageSaved
|
||||||
? context.lang.shareImagedEditorSavedImage
|
? context.lang.shareImagedEditorSavedImage
|
||||||
|
|
|
||||||
|
|
@ -2,29 +2,28 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class SendToWidget extends StatelessWidget {
|
class SendToWidget extends StatelessWidget {
|
||||||
final String sendTo;
|
|
||||||
|
|
||||||
const SendToWidget({
|
const SendToWidget({
|
||||||
super.key,
|
|
||||||
required this.sendTo,
|
required this.sendTo,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
final String sendTo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
TextStyle textStyle = TextStyle(
|
const textStyle = TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: const Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5.0,
|
blurRadius: 5,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle boldTextStyle = textStyle.copyWith(
|
final boldTextStyle = textStyle.copyWith(
|
||||||
fontWeight: FontWeight.normal,
|
fontWeight: FontWeight.normal,
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class VideoRecordingTimer extends StatelessWidget {
|
class VideoRecordingTimer extends StatelessWidget {
|
||||||
final DateTime? videoRecordingStarted;
|
|
||||||
final int maxVideoRecordingTime;
|
|
||||||
|
|
||||||
const VideoRecordingTimer({
|
const VideoRecordingTimer({
|
||||||
super.key,
|
|
||||||
required this.videoRecordingStarted,
|
required this.videoRecordingStarted,
|
||||||
required this.maxVideoRecordingTime,
|
required this.maxVideoRecordingTime,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
final DateTime? videoRecordingStarted;
|
||||||
|
final int maxVideoRecordingTime;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -26,11 +25,12 @@ class VideoRecordingTimer extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
value: (currentTime.difference(videoRecordingStarted!))
|
value: currentTime
|
||||||
|
.difference(videoRecordingStarted!)
|
||||||
.inMilliseconds /
|
.inMilliseconds /
|
||||||
(maxVideoRecordingTime * 1000),
|
(maxVideoRecordingTime * 1000),
|
||||||
strokeWidth: 4,
|
strokeWidth: 4,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
|
valueColor: const AlwaysStoppedAnimation<Color>(Colors.red),
|
||||||
backgroundColor: Colors.grey[300],
|
backgroundColor: Colors.grey[300],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -46,7 +46,7 @@ class VideoRecordingTimer extends StatelessWidget {
|
||||||
shadows: [
|
shadows: [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5.0,
|
blurRadius: 5,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
|
// ignore_for_file: avoid_dynamic_calls
|
||||||
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CameraZoomButtons extends StatefulWidget {
|
class CameraZoomButtons extends StatefulWidget {
|
||||||
const CameraZoomButtons(
|
const CameraZoomButtons({
|
||||||
{super.key,
|
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.updateScaleFactor,
|
required this.updateScaleFactor,
|
||||||
required this.scaleFactor});
|
required this.scaleFactor,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final CameraController controller;
|
final CameraController controller;
|
||||||
final double scaleFactor;
|
final double scaleFactor;
|
||||||
|
|
@ -20,7 +23,7 @@ class CameraZoomButtons extends StatefulWidget {
|
||||||
|
|
||||||
String beautifulZoomScale(double scale) {
|
String beautifulZoomScale(double scale) {
|
||||||
var tmp = scale.toStringAsFixed(1);
|
var tmp = scale.toStringAsFixed(1);
|
||||||
if (tmp[0] == "0") {
|
if (tmp[0] == '0') {
|
||||||
tmp = tmp.substring(1, tmp.length);
|
tmp = tmp.substring(1, tmp.length);
|
||||||
}
|
}
|
||||||
return tmp;
|
return tmp;
|
||||||
|
|
@ -50,20 +53,20 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var zoomButtonStyle = TextButton.styleFrom(
|
final zoomButtonStyle = TextButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
minimumSize: Size(40, 40),
|
minimumSize: const Size(40, 40),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
);
|
);
|
||||||
|
|
||||||
final zoomTextStyle = TextStyle(fontSize: 13);
|
const zoomTextStyle = TextStyle(fontSize: 13);
|
||||||
final isMiddleFocused = widget.scaleFactor >= 1 && widget.scaleFactor < 2;
|
final isMiddleFocused = widget.scaleFactor >= 1 && widget.scaleFactor < 2;
|
||||||
return Center(
|
return Center(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(40.0),
|
borderRadius: BorderRadius.circular(40),
|
||||||
child: Container(
|
child: ColoredBox(
|
||||||
color: const Color.fromARGB(90, 0, 0, 0),
|
color: const Color.fromARGB(90, 0, 0, 0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
@ -76,25 +79,24 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var level = await widget.controller.getMinZoomLevel();
|
final level = await widget.controller.getMinZoomLevel();
|
||||||
widget.updateScaleFactor(level);
|
widget.updateScaleFactor(level);
|
||||||
},
|
},
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: widget.controller.getMinZoomLevel(),
|
future: widget.controller.getMinZoomLevel(),
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.hasData) {
|
if (snap.hasData) {
|
||||||
var minLevel =
|
final minLevel = beautifulZoomScale(snap.data!);
|
||||||
beautifulZoomScale(snap.data!.toDouble());
|
final currentLevel =
|
||||||
var currentLevel =
|
|
||||||
beautifulZoomScale(widget.scaleFactor);
|
beautifulZoomScale(widget.scaleFactor);
|
||||||
return Text(
|
return Text(
|
||||||
widget.scaleFactor < 1
|
widget.scaleFactor < 1
|
||||||
? "${currentLevel}x"
|
? '${currentLevel}x'
|
||||||
: "${minLevel}x",
|
: '${minLevel}x',
|
||||||
style: zoomTextStyle,
|
style: zoomTextStyle,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Text("");
|
return const Text('');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -109,9 +111,9 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
widget.updateScaleFactor(1.0);
|
widget.updateScaleFactor(1.0);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
(isMiddleFocused)
|
isMiddleFocused
|
||||||
? "${beautifulZoomScale(widget.scaleFactor)}x"
|
? '${beautifulZoomScale(widget.scaleFactor)}x'
|
||||||
: "1.0x",
|
: '1.0x',
|
||||||
style: zoomTextStyle,
|
style: zoomTextStyle,
|
||||||
)),
|
)),
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|
@ -121,7 +123,8 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var level = min(await widget.controller.getMaxZoomLevel(), 2)
|
final level =
|
||||||
|
min(await widget.controller.getMaxZoomLevel(), 2)
|
||||||
.toDouble();
|
.toDouble();
|
||||||
widget.updateScaleFactor(level);
|
widget.updateScaleFactor(level);
|
||||||
},
|
},
|
||||||
|
|
@ -129,15 +132,15 @@ class _CameraZoomButtonsState extends State<CameraZoomButtons> {
|
||||||
future: widget.controller.getMaxZoomLevel(),
|
future: widget.controller.getMaxZoomLevel(),
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
if (snap.hasData) {
|
if (snap.hasData) {
|
||||||
var maxLevel = max(
|
final maxLevel = max(
|
||||||
min((snap.data?.toInt())!, 2),
|
min((snap.data?.toInt())!, 2),
|
||||||
widget.scaleFactor,
|
widget.scaleFactor,
|
||||||
);
|
);
|
||||||
return Text(
|
return Text(
|
||||||
"${beautifulZoomScale(maxLevel.toDouble())}x",
|
'${beautifulZoomScale(maxLevel.toDouble())}x',
|
||||||
style: zoomTextStyle);
|
style: zoomTextStyle);
|
||||||
} else {
|
} else {
|
||||||
return Text("");
|
return const Text('');
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -29,23 +29,25 @@ Future<(SelectedCameraDetails, CameraController)?> initializeCameraController(
|
||||||
SelectedCameraDetails details,
|
SelectedCameraDetails details,
|
||||||
int sCameraId,
|
int sCameraId,
|
||||||
bool init,
|
bool init,
|
||||||
bool enableAudio) async {
|
bool enableAudio,
|
||||||
if (sCameraId >= gCameras.length) return null;
|
) async {
|
||||||
|
var cameraId = sCameraId;
|
||||||
|
if (cameraId >= gCameras.length) return null;
|
||||||
if (init) {
|
if (init) {
|
||||||
for (; sCameraId < gCameras.length; sCameraId++) {
|
for (; cameraId < gCameras.length; cameraId++) {
|
||||||
if (gCameras[sCameraId].lensDirection == CameraLensDirection.back) {
|
if (gCameras[cameraId].lensDirection == CameraLensDirection.back) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
details.isZoomAble = false;
|
details.isZoomAble = false;
|
||||||
if (details.cameraId != sCameraId) {
|
if (details.cameraId != cameraId) {
|
||||||
// switch between front and back
|
// switch between front and back
|
||||||
details.scaleFactor = 1;
|
details.scaleFactor = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
final cameraController = CameraController(
|
final cameraController = CameraController(
|
||||||
gCameras[sCameraId],
|
gCameras[cameraId],
|
||||||
ResolutionPreset.high,
|
ResolutionPreset.high,
|
||||||
enableAudio: enableAudio,
|
enableAudio: enableAudio,
|
||||||
);
|
);
|
||||||
|
|
@ -64,9 +66,9 @@ Future<(SelectedCameraDetails, CameraController)?> initializeCameraController(
|
||||||
details
|
details
|
||||||
..isZoomAble = details.maxAvailableZoom != details.minAvailableZoom
|
..isZoomAble = details.maxAvailableZoom != details.minAvailableZoom
|
||||||
..cameraLoaded = true
|
..cameraLoaded = true
|
||||||
..cameraId = sCameraId;
|
..cameraId = cameraId;
|
||||||
}).catchError((Object e) {
|
}).catchError((Object e) {
|
||||||
Log.error("$e");
|
Log.error('$e');
|
||||||
});
|
});
|
||||||
return (details, cameraController);
|
return (details, cameraController);
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +215,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Future<Uint8List?> loadAndDeletePictureFromFile(XFile picture) async {
|
Future<Uint8List?> loadAndDeletePictureFromFile(XFile picture) async {
|
||||||
try {
|
try {
|
||||||
// Load the image into bytes
|
// Load the image into bytes
|
||||||
final Uint8List imageBytes = await picture.readAsBytes();
|
final imageBytes = await picture.readAsBytes();
|
||||||
// Remove the image file
|
// Remove the image file
|
||||||
await File(picture.path).delete();
|
await File(picture.path).delete();
|
||||||
return imageBytes;
|
return imageBytes;
|
||||||
|
|
@ -400,7 +402,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
videoRecordingTimer = null;
|
videoRecordingTimer = null;
|
||||||
}
|
}
|
||||||
if (cameraController == null || !cameraController!.value.isRecordingVideo) {
|
if (cameraController == null || !cameraController!.value.isRecordingVideo) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -414,8 +416,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
if (videoPath != null) {
|
if (videoPath != null) {
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
// see https://github.com/flutter/flutter/issues/148335
|
// see https://github.com/flutter/flutter/issues/148335
|
||||||
await File(videoPath.path).rename("${videoPath.path}.mp4");
|
await File(videoPath.path).rename('${videoPath.path}.mp4');
|
||||||
videoPathFile = File("${videoPath.path}.mp4");
|
videoPathFile = File('${videoPath.path}.mp4');
|
||||||
} else {
|
} else {
|
||||||
videoPathFile = File(videoPath.path);
|
videoPathFile = File(videoPath.path);
|
||||||
}
|
}
|
||||||
|
|
@ -426,12 +428,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
} on CameraException catch (e) {
|
} on CameraException catch (e) {
|
||||||
_showCameraException(e);
|
_showCameraException(e);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showCameraException(dynamic e) {
|
void _showCameraException(dynamic e) {
|
||||||
Log.error("$e");
|
Log.error('$e');
|
||||||
try {
|
try {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
|
|
@ -536,10 +538,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
: Colors.white.withAlpha(160),
|
: Colors.white.withAlpha(160),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (selectedCameraDetails.isFlashOn) {
|
if (selectedCameraDetails.isFlashOn) {
|
||||||
cameraController?.setFlashMode(FlashMode.off);
|
await cameraController
|
||||||
|
?.setFlashMode(FlashMode.off);
|
||||||
selectedCameraDetails.isFlashOn = false;
|
selectedCameraDetails.isFlashOn = false;
|
||||||
} else {
|
} else {
|
||||||
cameraController?.setFlashMode(FlashMode.always);
|
await cameraController
|
||||||
|
?.setFlashMode(FlashMode.always);
|
||||||
selectedCameraDetails.isFlashOn = true;
|
selectedCameraDetails.isFlashOn = true;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
@ -566,16 +570,16 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
Icons.mic_off_rounded,
|
Icons.mic_off_rounded,
|
||||||
color: Colors.white.withAlpha(160),
|
color: Colors.white.withAlpha(160),
|
||||||
tooltipText:
|
tooltipText:
|
||||||
"Allow microphone access for video recording.",
|
'Allow microphone access for video recording.',
|
||||||
onPressed: requestMicrophonePermission,
|
onPressed: requestMicrophonePermission,
|
||||||
),
|
),
|
||||||
if (hasAudioPermission)
|
if (hasAudioPermission)
|
||||||
ActionButton(
|
ActionButton(
|
||||||
(videoWithAudio)
|
videoWithAudio
|
||||||
? Icons.volume_up_rounded
|
? Icons.volume_up_rounded
|
||||||
: Icons.volume_off_rounded,
|
: Icons.volume_off_rounded,
|
||||||
tooltipText: "Record video with audio.",
|
tooltipText: 'Record video with audio.',
|
||||||
color: (videoWithAudio)
|
color: videoWithAudio
|
||||||
? Colors.white
|
? Colors.white
|
||||||
: Colors.white.withAlpha(160),
|
: Colors.white.withAlpha(160),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
|
@ -614,13 +618,11 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
if (!isVideoRecording)
|
if (!isVideoRecording)
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: pickImageFromGallery,
|
onTap: pickImageFromGallery,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 80,
|
width: 80,
|
||||||
|
|
@ -640,7 +642,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
// onLongPress: startVideoRecording,
|
// onLongPress: startVideoRecording,
|
||||||
key: keyTriggerButton,
|
key: keyTriggerButton,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 100,
|
height: 100,
|
||||||
width: 100,
|
width: 100,
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
Future<void> toggleSelectedCamera() async {
|
Future<void> toggleSelectedCamera() async {
|
||||||
await cameraController?.dispose();
|
await cameraController?.dispose();
|
||||||
cameraController = null;
|
cameraController = null;
|
||||||
selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
|
await selectCamera((selectedCameraDetails.cameraId + 1) % 2, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -54,7 +54,7 @@ class CameraSendToViewState extends State<CameraSendToView> {
|
||||||
onDoubleTap: toggleSelectedCamera,
|
onDoubleTap: toggleSelectedCamera,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
SendToCameraPreview(),
|
const SendToCameraPreview(),
|
||||||
CameraPreviewControllerView(
|
CameraPreviewControllerView(
|
||||||
selectCamera: selectCamera,
|
selectCamera: selectCamera,
|
||||||
sendTo: widget.sendTo,
|
sendTo: widget.sendTo,
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,20 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
class ActionButton extends StatelessWidget {
|
class ActionButton extends StatelessWidget {
|
||||||
|
const ActionButton(
|
||||||
|
this.icon, {
|
||||||
|
required this.tooltipText,
|
||||||
|
super.key,
|
||||||
|
this.onPressed,
|
||||||
|
this.color,
|
||||||
|
this.disable = false,
|
||||||
|
});
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
final IconData? icon;
|
final IconData? icon;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final String tooltipText;
|
final String tooltipText;
|
||||||
final bool disable;
|
final bool disable;
|
||||||
|
|
||||||
const ActionButton(this.icon,
|
|
||||||
{super.key,
|
|
||||||
this.onPressed,
|
|
||||||
this.color,
|
|
||||||
required this.tooltipText,
|
|
||||||
this.disable = false});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
|
|
@ -26,10 +27,10 @@ class ActionButton extends StatelessWidget {
|
||||||
color: disable
|
color: disable
|
||||||
? const Color.fromARGB(154, 255, 255, 255)
|
? const Color.fromARGB(154, 255, 255, 255)
|
||||||
: color ?? Colors.white,
|
: color ?? Colors.white,
|
||||||
shadows: [
|
shadows: const [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: const Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5.0,
|
blurRadius: 5,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ImageItem {
|
class ImageItem {
|
||||||
|
ImageItem([dynamic image]) {
|
||||||
|
if (image != null) load(image);
|
||||||
|
}
|
||||||
int width = 1;
|
int width = 1;
|
||||||
int height = 1;
|
int height = 1;
|
||||||
Uint8List bytes = Uint8List.fromList([]);
|
Uint8List bytes = Uint8List.fromList([]);
|
||||||
Completer<bool> loader = Completer<bool>();
|
Completer<bool> loader = Completer<bool>();
|
||||||
|
|
||||||
ImageItem([dynamic image]) {
|
|
||||||
if (image != null) load(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> load(dynamic image) async {
|
Future<void> load(dynamic image) async {
|
||||||
loader = Completer<bool>();
|
loader = Completer<bool>();
|
||||||
|
|
||||||
|
|
@ -24,7 +23,7 @@ class ImageItem {
|
||||||
return loader.complete(true);
|
return loader.complete(true);
|
||||||
} else if (image is Uint8List) {
|
} else if (image is Uint8List) {
|
||||||
bytes = image;
|
bytes = image;
|
||||||
var decodedImage = await decodeImageFromList(bytes);
|
final decodedImage = await decodeImageFromList(bytes);
|
||||||
|
|
||||||
height = decodedImage.height;
|
height = decodedImage.height;
|
||||||
width = decodedImage.width;
|
width = decodedImage.width;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
|
// ignore_for_file: comment_references
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hand_signature/signature.dart';
|
import 'package:hand_signature/signature.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
|
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
|
||||||
|
|
||||||
/// Layer class with some common properties
|
/// Layer class with some common properties
|
||||||
class Layer {
|
class Layer {
|
||||||
Offset offset;
|
|
||||||
double rotation, scale, opacity;
|
|
||||||
bool isEditing;
|
|
||||||
bool isDeleted;
|
|
||||||
bool hasCustomActionButtons;
|
|
||||||
bool showCustomButtons;
|
|
||||||
|
|
||||||
Layer({
|
Layer({
|
||||||
this.offset = const Offset(0, 0),
|
this.offset = Offset.zero,
|
||||||
this.opacity = 1,
|
this.opacity = 1,
|
||||||
this.isEditing = false,
|
this.isEditing = false,
|
||||||
this.isDeleted = false,
|
this.isDeleted = false,
|
||||||
|
|
@ -21,24 +16,28 @@ class Layer {
|
||||||
this.rotation = 0,
|
this.rotation = 0,
|
||||||
this.scale = 1,
|
this.scale = 1,
|
||||||
});
|
});
|
||||||
|
Offset offset;
|
||||||
|
double rotation;
|
||||||
|
double scale;
|
||||||
|
double opacity;
|
||||||
|
bool isEditing;
|
||||||
|
bool isDeleted;
|
||||||
|
bool hasCustomActionButtons;
|
||||||
|
bool showCustomButtons;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [BackgroundLayer]
|
/// Attributes used by [BackgroundLayer]
|
||||||
class BackgroundLayerData extends Layer {
|
class BackgroundLayerData extends Layer {
|
||||||
ImageItem image;
|
|
||||||
|
|
||||||
BackgroundLayerData({
|
BackgroundLayerData({
|
||||||
required this.image,
|
required this.image,
|
||||||
});
|
});
|
||||||
|
ImageItem image;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilterLayerData extends Layer {}
|
class FilterLayerData extends Layer {}
|
||||||
|
|
||||||
/// Attributes used by [EmojiLayer]
|
/// Attributes used by [EmojiLayer]
|
||||||
class EmojiLayerData extends Layer {
|
class EmojiLayerData extends Layer {
|
||||||
String text;
|
|
||||||
double size;
|
|
||||||
|
|
||||||
EmojiLayerData({
|
EmojiLayerData({
|
||||||
this.text = '',
|
this.text = '',
|
||||||
this.size = 64,
|
this.size = 64,
|
||||||
|
|
@ -48,31 +47,27 @@ class EmojiLayerData extends Layer {
|
||||||
super.scale,
|
super.scale,
|
||||||
super.isEditing,
|
super.isEditing,
|
||||||
});
|
});
|
||||||
|
String text;
|
||||||
|
double size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [TextLayer]
|
/// Attributes used by [TextLayer]
|
||||||
class TextLayerData extends Layer {
|
class TextLayerData extends Layer {
|
||||||
String text;
|
|
||||||
int textLayersBefore;
|
|
||||||
TextLayerData({
|
TextLayerData({
|
||||||
this.text = "",
|
|
||||||
required this.textLayersBefore,
|
required this.textLayersBefore,
|
||||||
|
this.text = '',
|
||||||
super.offset,
|
super.offset,
|
||||||
super.opacity,
|
super.opacity,
|
||||||
super.rotation,
|
super.rotation,
|
||||||
super.scale,
|
super.scale,
|
||||||
super.isEditing = true,
|
super.isEditing = true,
|
||||||
});
|
});
|
||||||
|
String text;
|
||||||
|
int textLayersBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attributes used by [DrawLayer]
|
/// Attributes used by [DrawLayer]
|
||||||
class DrawLayerData extends Layer {
|
class DrawLayerData extends Layer {
|
||||||
final control = HandSignatureControl(
|
|
||||||
threshold: 3.0,
|
|
||||||
smoothRatio: 0.65,
|
|
||||||
velocityRange: 2.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
// String text;
|
// String text;
|
||||||
DrawLayerData({
|
DrawLayerData({
|
||||||
super.offset,
|
super.offset,
|
||||||
|
|
@ -82,4 +77,5 @@ class DrawLayerData extends Layer {
|
||||||
super.hasCustomActionButtons = true,
|
super.hasCustomActionButtons = true,
|
||||||
super.isEditing = true,
|
super.isEditing = true,
|
||||||
});
|
});
|
||||||
|
final control = HandSignatureControl();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,13 @@ import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||||
|
|
||||||
/// Main layer
|
/// Main layer
|
||||||
class BackgroundLayer extends StatefulWidget {
|
class BackgroundLayer extends StatefulWidget {
|
||||||
final BackgroundLayerData layerData;
|
|
||||||
final VoidCallback? onUpdate;
|
|
||||||
|
|
||||||
const BackgroundLayer({
|
const BackgroundLayer({
|
||||||
super.key,
|
|
||||||
required this.layerData,
|
required this.layerData,
|
||||||
|
super.key,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
});
|
});
|
||||||
|
final BackgroundLayerData layerData;
|
||||||
|
final VoidCallback? onUpdate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BackgroundLayer> createState() => _BackgroundLayerState();
|
State<BackgroundLayer> createState() => _BackgroundLayerState();
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,26 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:hand_signature/signature.dart';
|
import 'package:hand_signature/signature.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
|
|
||||||
class DrawLayer extends StatefulWidget {
|
class DrawLayer extends StatefulWidget {
|
||||||
final DrawLayerData layerData;
|
|
||||||
final VoidCallback? onUpdate;
|
|
||||||
|
|
||||||
const DrawLayer({
|
const DrawLayer({
|
||||||
super.key,
|
|
||||||
required this.layerData,
|
required this.layerData,
|
||||||
|
super.key,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
});
|
});
|
||||||
|
final DrawLayerData layerData;
|
||||||
|
final VoidCallback? onUpdate;
|
||||||
@override
|
@override
|
||||||
createState() => _DrawLayerState();
|
State<DrawLayer> createState() => _DrawLayerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DrawLayerState extends State<DrawLayer> {
|
class _DrawLayerState extends State<DrawLayer> {
|
||||||
Color currentColor = Colors.red;
|
Color currentColor = Colors.red;
|
||||||
|
|
||||||
var screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
||||||
List<CubicPath> undoList = [];
|
List<CubicPath> undoList = [];
|
||||||
bool skipNextEvent = false;
|
bool skipNextEvent = false;
|
||||||
|
|
@ -48,7 +47,7 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
|
|
||||||
double _sliderValue = 0.125;
|
double _sliderValue = 0.125;
|
||||||
|
|
||||||
final colors = [
|
final List<Color> colors = [
|
||||||
Colors.white,
|
Colors.white,
|
||||||
Colors.red,
|
Colors.red,
|
||||||
Colors.orange,
|
Colors.orange,
|
||||||
|
|
@ -61,11 +60,11 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
|
|
||||||
Color _getColorFromSliderValue(double value) {
|
Color _getColorFromSliderValue(double value) {
|
||||||
// Calculate the index based on the slider value
|
// Calculate the index based on the slider value
|
||||||
int index = (value * (colors.length - 1)).floor();
|
final index = (value * (colors.length - 1)).floor();
|
||||||
int nextIndex = (index + 1).clamp(0, colors.length - 1);
|
final nextIndex = (index + 1).clamp(0, colors.length - 1);
|
||||||
|
|
||||||
// Calculate the interpolation factor
|
// Calculate the interpolation factor
|
||||||
double factor = value * (colors.length - 1) - index;
|
final factor = value * (colors.length - 1) - index;
|
||||||
|
|
||||||
// Interpolate between the two colors
|
// Interpolate between the two colors
|
||||||
return Color.lerp(colors[index], colors[nextIndex], factor)!;
|
return Color.lerp(colors[index], colors[nextIndex], factor)!;
|
||||||
|
|
@ -85,17 +84,14 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
children: [
|
children: [
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
),
|
),
|
||||||
child: Screenshot(
|
child: Screenshot(
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
child: HandSignature(
|
child: HandSignature(
|
||||||
control: widget.layerData.control,
|
control: widget.layerData.control,
|
||||||
color: currentColor,
|
drawer: LineSignatureDrawer(color: currentColor, width: 7),
|
||||||
width: 1.0,
|
|
||||||
maxWidth: 7.0,
|
|
||||||
type: SignatureDrawType.shape,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -112,9 +108,7 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
tooltipText: context.lang.imageEditorDrawOk,
|
tooltipText: context.lang.imageEditorDrawOk,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
widget.layerData.isEditing = false;
|
widget.layerData.isEditing = false;
|
||||||
if (widget.onUpdate != null) {
|
|
||||||
widget.onUpdate!();
|
widget.onUpdate!();
|
||||||
}
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -200,8 +194,6 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
showMagnifyingGlass = false;
|
showMagnifyingGlass = false;
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
min: 0.0,
|
|
||||||
max: 1.0,
|
|
||||||
divisions: 100,
|
divisions: 100,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -226,10 +218,9 @@ class _DrawLayerState extends State<DrawLayer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MagnifyingGlass extends StatelessWidget {
|
class MagnifyingGlass extends StatelessWidget {
|
||||||
|
const MagnifyingGlass({required this.color, super.key});
|
||||||
final Color color;
|
final Color color;
|
||||||
|
|
||||||
const MagnifyingGlass({super.key, required this.color});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,8 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
}
|
}
|
||||||
if (deleteLayer) {
|
if (deleteLayer) {
|
||||||
widget.layerData.isDeleted = true;
|
widget.layerData.isDeleted = true;
|
||||||
if (widget.onUpdate != null) {
|
|
||||||
widget.onUpdate!();
|
widget.onUpdate!();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onPointerDown: (details) {
|
onPointerDown: (details) {
|
||||||
|
|
@ -138,7 +136,7 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
padding: const EdgeInsets.all(44),
|
padding: const EdgeInsets.all(44),
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.layerData.text.toString(),
|
widget.layerData.text,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: widget.layerData.size,
|
fontSize: widget.layerData.size,
|
||||||
),
|
),
|
||||||
|
|
@ -157,7 +155,7 @@ class _EmojiLayerState extends State<EmojiLayer> {
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: ActionButton(
|
child: ActionButton(
|
||||||
FontAwesomeIcons.trashCan,
|
FontAwesomeIcons.trashCan,
|
||||||
tooltipText: "",
|
tooltipText: '',
|
||||||
color: deleteLayer ? Colors.red : Colors.white,
|
color: deleteLayer ? Colors.red : Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ class FilterText extends StatelessWidget {
|
||||||
shadows: const [
|
shadows: const [
|
||||||
Shadow(
|
Shadow(
|
||||||
color: Color.fromARGB(122, 0, 0, 0),
|
color: Color.fromARGB(122, 0, 0, 0),
|
||||||
blurRadius: 5.0,
|
blurRadius: 5,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ class DateTimeFilter extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String currentTime = DateFormat('HH:mm').format(DateTime.now());
|
final currentTime = DateFormat('HH:mm').format(DateTime.now());
|
||||||
String currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now());
|
final currentDate = DateFormat('dd.MM.yyyy').format(DateTime.now());
|
||||||
return FilterSkeleton(
|
return FilterSkeleton(
|
||||||
child: Positioned(
|
child: Positioned(
|
||||||
bottom: 80,
|
bottom: 80,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
|
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
|
||||||
|
|
||||||
class ImageFilter extends StatelessWidget {
|
class ImageFilter extends StatelessWidget {
|
||||||
const ImageFilter({super.key, required this.imagePath});
|
const ImageFilter({required this.imagePath, super.key});
|
||||||
final String imagePath;
|
final String imagePath;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -15,7 +15,7 @@ class ImageFilter extends StatelessWidget {
|
||||||
right: 10,
|
right: 10,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: "https://twonly.eu/$imagePath",
|
imageUrl: 'https://twonly.eu/$imagePath',
|
||||||
height: 150,
|
height: 150,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,6 @@ class _LocationFilterState extends State<LocationFilter> {
|
||||||
bottom: 50,
|
bottom: 50,
|
||||||
left: 40,
|
left: 40,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
FilterText(location!.city),
|
FilterText(location!.city),
|
||||||
FilterText(location!.county),
|
FilterText(location!.county),
|
||||||
|
|
@ -131,8 +130,8 @@ Future<List<Sticker>> getStickerIndex() async {
|
||||||
final indexFile = File('${directory.path}/stickers.json');
|
final indexFile = File('${directory.path}/stickers.json');
|
||||||
var res = <Sticker>[];
|
var res = <Sticker>[];
|
||||||
|
|
||||||
if (await indexFile.exists() && !kDebugMode) {
|
if (indexFile.existsSync() && !kDebugMode) {
|
||||||
final lastModified = await indexFile.lastModified();
|
final lastModified = indexFile.lastModifiedSync();
|
||||||
final difference = DateTime.now().difference(lastModified);
|
final difference = DateTime.now().difference(lastModified);
|
||||||
final content = await indexFile.readAsString();
|
final content = await indexFile.readAsString();
|
||||||
final jsonList = json.decode(content) as List;
|
final jsonList = json.decode(content) as List;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: prefer_null_aware_method_calls
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
@ -8,16 +10,15 @@ import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||||
|
|
||||||
/// Text layer
|
/// Text layer
|
||||||
class TextLayer extends StatefulWidget {
|
class TextLayer extends StatefulWidget {
|
||||||
final TextLayerData layerData;
|
|
||||||
final VoidCallback? onUpdate;
|
|
||||||
|
|
||||||
const TextLayer({
|
const TextLayer({
|
||||||
super.key,
|
|
||||||
required this.layerData,
|
required this.layerData,
|
||||||
|
super.key,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
});
|
});
|
||||||
|
final TextLayerData layerData;
|
||||||
|
final VoidCallback? onUpdate;
|
||||||
@override
|
@override
|
||||||
createState() => _TextViewState();
|
State<TextLayer> createState() => _TextViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TextViewState extends State<TextLayer> {
|
class _TextViewState extends State<TextLayer> {
|
||||||
|
|
@ -69,7 +70,7 @@ class _TextViewState extends State<TextLayer> {
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
onEditingComplete: () {
|
onEditingComplete: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.layerData.isDeleted = textController.text == "";
|
widget.layerData.isDeleted = textController.text == '';
|
||||||
widget.layerData.isEditing = false;
|
widget.layerData.isEditing = false;
|
||||||
widget.layerData.text = textController.text;
|
widget.layerData.text = textController.text;
|
||||||
});
|
});
|
||||||
|
|
@ -83,10 +84,10 @@ class _TextViewState extends State<TextLayer> {
|
||||||
},
|
},
|
||||||
onTapOutside: (a) {
|
onTapOutside: (a) {
|
||||||
widget.layerData.text = textController.text;
|
widget.layerData.text = textController.text;
|
||||||
Future.delayed(Duration(milliseconds: 100), () {
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
widget.layerData.isDeleted = textController.text == "";
|
widget.layerData.isDeleted = textController.text == '';
|
||||||
widget.layerData.isEditing = false;
|
widget.layerData.isEditing = false;
|
||||||
context
|
context
|
||||||
.read<ImageEditorProvider>()
|
.read<ImageEditorProvider>()
|
||||||
|
|
@ -102,10 +103,10 @@ class _TextViewState extends State<TextLayer> {
|
||||||
.read<ImageEditorProvider>()
|
.read<ImageEditorProvider>()
|
||||||
.updateSomeTextViewIsAlreadyEditing(false);
|
.updateSomeTextViewIsAlreadyEditing(false);
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: const InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: 8,
|
horizontal: 8,
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
|
|
@ -136,7 +137,7 @@ class _TextViewState extends State<TextLayer> {
|
||||||
onScaleEnd: (d) {
|
onScaleEnd: (d) {
|
||||||
if (deleteLayer) {
|
if (deleteLayer) {
|
||||||
widget.layerData.isDeleted = true;
|
widget.layerData.isDeleted = true;
|
||||||
textController.text = "";
|
textController.text = '';
|
||||||
}
|
}
|
||||||
elementIsScaled = false;
|
elementIsScaled = false;
|
||||||
if (widget.onUpdate != null) {
|
if (widget.onUpdate != null) {
|
||||||
|
|
@ -161,8 +162,8 @@ class _TextViewState extends State<TextLayer> {
|
||||||
widget.layerData.offset = Offset(
|
widget.layerData.offset = Offset(
|
||||||
0, widget.layerData.offset.dy + detail.focalPointDelta.dy);
|
0, widget.layerData.offset.dy + detail.focalPointDelta.dy);
|
||||||
}
|
}
|
||||||
final RenderBox renderBox =
|
final renderBox =
|
||||||
_widgetKey.currentContext!.findRenderObject() as RenderBox;
|
_widgetKey.currentContext!.findRenderObject()! as RenderBox;
|
||||||
|
|
||||||
if (widget.layerData.offset.dy > renderBox.size.height - 80) {
|
if (widget.layerData.offset.dy > renderBox.size.height - 80) {
|
||||||
if (!deleteLayer) {
|
if (!deleteLayer) {
|
||||||
|
|
@ -198,11 +199,11 @@ class _TextViewState extends State<TextLayer> {
|
||||||
child: Center(
|
child: Center(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTapUp: (d) {
|
onTapUp: (d) {
|
||||||
textController.text = "";
|
textController.text = '';
|
||||||
},
|
},
|
||||||
child: ActionButton(
|
child: ActionButton(
|
||||||
FontAwesomeIcons.trashCan,
|
FontAwesomeIcons.trashCan,
|
||||||
tooltipText: "",
|
tooltipText: '',
|
||||||
color: deleteLayer ? Colors.red : Colors.white,
|
color: deleteLayer ? Colors.red : Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ class Emojis extends StatefulWidget {
|
||||||
const Emojis({super.key});
|
const Emojis({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
createState() => _EmojisState();
|
State<Emojis> createState() => _EmojisState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EmojisState extends State<Emojis> {
|
class _EmojisState extends State<Emojis> {
|
||||||
|
|
@ -57,7 +57,7 @@ class _EmojisState extends State<Emojis> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(0.0),
|
padding: EdgeInsets.zero,
|
||||||
height: 400,
|
height: 400,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
|
|
@ -77,14 +77,12 @@ class _EmojisState extends State<Emojis> {
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Container(
|
Container(
|
||||||
height: 315,
|
height: 315,
|
||||||
padding: const EdgeInsets.all(0.0),
|
padding: EdgeInsets.zero,
|
||||||
child: GridView(
|
child: GridView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const ClampingScrollPhysics(),
|
physics: const ClampingScrollPhysics(),
|
||||||
scrollDirection: Axis.vertical,
|
|
||||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
mainAxisSpacing: 0.0,
|
maxCrossAxisExtent: 60,
|
||||||
maxCrossAxisExtent: 60.0,
|
|
||||||
),
|
),
|
||||||
children: lastUsed.map((String emoji) {
|
children: lastUsed.map((String emoji) {
|
||||||
return GridTile(
|
return GridTile(
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class BestFriendsSelector extends StatelessWidget {
|
||||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Text(context.lang.shareImagedSelectAll,
|
child: Text(context.lang.shareImagedSelectAll,
|
||||||
style: const TextStyle(fontSize: 10)),
|
style: const TextStyle(fontSize: 10)),
|
||||||
|
|
@ -143,7 +143,7 @@ class UserCheckbox extends StatelessWidget {
|
||||||
color: Color.fromRGBO(0, 0, 0, 0.1),
|
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
borderRadius: BorderRadius.circular(8.0),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
bool loadingImage = true;
|
bool loadingImage = true;
|
||||||
bool isDisposed = false;
|
bool isDisposed = false;
|
||||||
HashSet<int> selectedUserIds = HashSet();
|
HashSet<int> selectedUserIds = HashSet();
|
||||||
double widthRatio = 1, heightRatio = 1, pixelRatio = 1;
|
double widthRatio = 1;
|
||||||
|
double heightRatio = 1;
|
||||||
|
double pixelRatio = 1;
|
||||||
VideoPlayerController? videoController;
|
VideoPlayerController? videoController;
|
||||||
ImageItem currentImage = ImageItem();
|
ImageItem currentImage = ImageItem();
|
||||||
ScreenshotController screenshotController = ScreenshotController();
|
ScreenshotController screenshotController = ScreenshotController();
|
||||||
|
|
@ -93,7 +95,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initAsync() async {
|
Future<void> initAsync() async {
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
if (user.defaultShowTime != null) {
|
if (user.defaultShowTime != null) {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
final TextEditingController searchUserName = TextEditingController();
|
final TextEditingController searchUserName = TextEditingController();
|
||||||
bool showRealTwonlyWarning = false;
|
bool showRealTwonlyWarning = false;
|
||||||
late StreamSubscription<List<Contact>> contactSub;
|
late StreamSubscription<List<Contact>> contactSub;
|
||||||
String lastQuery = "";
|
String lastQuery = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,9 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
margin: EdgeInsets.all(10),
|
margin: const EdgeInsets.all(10),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -52,7 +52,7 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
const SizedBox(height: 5),
|
||||||
Text(
|
Text(
|
||||||
context.lang.backupNoticeDesc,
|
context.lang.backupNoticeDesc,
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
|
|
@ -64,20 +64,20 @@ class _BackupNoticeCardState extends State<BackupNoticeCard> {
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.nextTimeToShowBackupNotice =
|
user.nextTimeToShowBackupNotice =
|
||||||
DateTime.now().add(Duration(days: 7));
|
DateTime.now().add(const Duration(days: 7));
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
initAsync();
|
await initAsync();
|
||||||
},
|
},
|
||||||
child: Text(context.lang.backupNoticeLater),
|
child: Text(context.lang.backupNoticeLater),
|
||||||
),
|
),
|
||||||
SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BackupView(),
|
builder: (context) => const BackupView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class _ConnectionInfoWidgetState extends State<ConnectionInfo>
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!showAnimation || gIsDemoUser) return Container();
|
if (!showAnimation || gIsDemoUser) return Container();
|
||||||
double screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: screenWidth,
|
width: screenWidth,
|
||||||
|
|
@ -70,8 +70,8 @@ class _ConnectionInfoWidgetState extends State<ConnectionInfo>
|
||||||
child: AnimatedBuilder(
|
child: AnimatedBuilder(
|
||||||
animation: _controller,
|
animation: _controller,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
double barWidth = _widthAnim.value;
|
final barWidth = _widthAnim.value;
|
||||||
double left = _positionAnim.value * (screenWidth - barWidth);
|
final left = _positionAnim.value * (screenWidth - barWidth);
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ class DemoUserCard extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return ColoredBox(
|
||||||
color: isDarkMode(context) ? Colors.white : Colors.black,
|
color: isDarkMode(context) ? Colors.white : Colors.black,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'This is a Demo-Preview.',
|
'This is a Demo-Preview.',
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
twonlyDB.messagesDao.openedAllNonMediaMessages(widget.contact.userId);
|
await twonlyDB.messagesDao
|
||||||
|
.openedAllNonMediaMessages(widget.contact.userId);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
textReactionsToMessageId = tmpTextReactionsToMessageId;
|
textReactionsToMessageId = tmpTextReactionsToMessageId;
|
||||||
|
|
@ -271,7 +272,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: ColoredBox(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart';
|
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
|
||||||
import 'package:twonly/src/services/api/media_download.dart' as received;
|
|
||||||
|
|
||||||
import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
||||||
|
import 'package:twonly/src/services/api/media_download.dart' as received;
|
||||||
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
|
import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart';
|
||||||
|
import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
||||||
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
||||||
|
|
||||||
class ChatMediaEntry extends StatefulWidget {
|
class ChatMediaEntry extends StatefulWidget {
|
||||||
const ChatMediaEntry({
|
const ChatMediaEntry({
|
||||||
super.key,
|
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.contact,
|
required this.contact,
|
||||||
required this.content,
|
required this.content,
|
||||||
required this.galleryItems,
|
required this.galleryItems,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Message message;
|
final Message message;
|
||||||
|
|
@ -47,13 +46,13 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
widget.message.mediaStored) {
|
widget.message.mediaStored) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await received.existsMediaFile(widget.message.messageId, "png")) {
|
if (await received.existsMediaFile(widget.message.messageId, 'png')) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
canBeReopened = true;
|
canBeReopened = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Future.delayed(Duration(seconds: 1), () {
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
showReopenMediaFilesTutorial(context, reopenMediaFile);
|
showReopenMediaFilesTutorial(context, reopenMediaFile);
|
||||||
});
|
});
|
||||||
|
|
@ -62,7 +61,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Color color = getMessageColorFromType(
|
final color = getMessageColorFromType(
|
||||||
widget.content,
|
widget.content,
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
|
@ -75,7 +74,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
widget.message.mediaStored) {
|
widget.message.mediaStored) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await received.existsMediaFile(widget.message.messageId, "png")) {
|
if (await received.existsMediaFile(widget.message.messageId, 'png')) {
|
||||||
await encryptAndSendMessageAsync(
|
await encryptAndSendMessageAsync(
|
||||||
null,
|
null,
|
||||||
widget.contact.userId,
|
widget.contact.userId,
|
||||||
|
|
@ -93,7 +92,7 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
);
|
);
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
widget.message.messageId,
|
widget.message.messageId,
|
||||||
MessagesCompanion(openedAt: Value(null)),
|
const MessagesCompanion(openedAt: Value(null)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -108,9 +107,9 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
initialMessage: widget.message);
|
initialMessage: widget.message);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
checkIfTutorialCanBeShown();
|
await checkIfTutorialCanBeShown();
|
||||||
} else if (widget.message.downloadState == DownloadState.pending) {
|
} else if (widget.message.downloadState == DownloadState.pending) {
|
||||||
received.startDownloadMedia(widget.message, true);
|
await received.startDownloadMedia(widget.message, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/views/components/better_text.dart';
|
import 'package:twonly/src/views/components/better_text.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
|
||||||
|
|
||||||
class ChatTextEntry extends StatelessWidget {
|
class ChatTextEntry extends StatelessWidget {
|
||||||
const ChatTextEntry({super.key, required this.message, required this.text});
|
const ChatTextEntry({required this.message, required this.text, super.key});
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
final Message message;
|
final Message message;
|
||||||
|
|
@ -14,10 +14,10 @@ class ChatTextEntry extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (EmojiAnimation.supported(text)) {
|
if (EmojiAnimation.supported(text)) {
|
||||||
return Container(
|
return Container(
|
||||||
constraints: BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
maxWidth: 100,
|
maxWidth: 100,
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
horizontal: 10,
|
horizontal: 10,
|
||||||
),
|
),
|
||||||
|
|
@ -28,10 +28,10 @@ class ChatTextEntry extends StatelessWidget {
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 10),
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: getMessageColor(message),
|
color: getMessageColor(message),
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: BetterText(text: text),
|
child: BetterText(text: text),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,7 @@ class ChatTextResponseColumns extends StatelessWidget {
|
||||||
children.insert(
|
children.insert(
|
||||||
0,
|
0,
|
||||||
Container(
|
Container(
|
||||||
padding:
|
padding: const EdgeInsets.only(top: 5, right: 10, left: 10),
|
||||||
const EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
|
|
||||||
|
|
@ -34,18 +34,18 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
||||||
backupServer,
|
backupServer,
|
||||||
);
|
);
|
||||||
|
|
||||||
Restart.restartApp(
|
await Restart.restartApp(
|
||||||
notificationTitle: 'Backup successfully recovered.',
|
notificationTitle: 'Backup successfully recovered.',
|
||||||
notificationBody: 'Click here to open the app again',
|
notificationBody: 'Click here to open the app again',
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// in case something was already written from the backup...
|
// in case something was already written from the backup...
|
||||||
Log.error("$e");
|
Log.error('$e');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('$e'),
|
content: Text('$e'),
|
||||||
duration: Duration(seconds: 3),
|
duration: const Duration(seconds: 3),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -60,23 +60,24 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text("twonly Safe ${context.lang.twonlySafeRecoverTitle}"),
|
title: Text('twonly Safe ${context.lang.twonlySafeRecoverTitle}'),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showAlertDialog(
|
showAlertDialog(
|
||||||
context,
|
context,
|
||||||
"twonly Safe",
|
'twonly Safe',
|
||||||
context.lang.backupTwonlySafeLongDesc,
|
context.lang.backupTwonlySafeLongDesc,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: FaIcon(FontAwesomeIcons.circleInfo),
|
icon: const FaIcon(FontAwesomeIcons.circleInfo),
|
||||||
iconSize: 18,
|
iconSize: 18,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40),
|
padding:
|
||||||
|
const EdgeInsetsGeometry.symmetric(vertical: 40, horizontal: 40),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -95,7 +96,7 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
||||||
context.lang.registerUsernameDecoration,
|
context.lang.registerUsernameDecoration,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
|
|
@ -130,30 +131,30 @@ class _BackupRecoveryViewState extends State<BackupRecoveryView> {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
Center(
|
Center(
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
backupServer = await Navigator.push(context,
|
backupServer = await Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return TwonlySafeServerView();
|
return const TwonlySafeServerView();
|
||||||
}));
|
}));
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Text(context.lang.backupExpertSettings),
|
child: Text(context.lang.backupExpertSettings),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Center(
|
Center(
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: (!isLoading) ? _recoverTwonlySafe : null,
|
onPressed: (!isLoading) ? _recoverTwonlySafe : null,
|
||||||
icon: isLoading
|
icon: isLoading
|
||||||
? SizedBox(
|
? const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
width: 12,
|
width: 12,
|
||||||
child: CircularProgressIndicator(strokeWidth: 1),
|
child: CircularProgressIndicator(strokeWidth: 1),
|
||||||
)
|
)
|
||||||
: Icon(Icons.lock_clock_rounded),
|
: const Icon(Icons.lock_clock_rounded),
|
||||||
label: Text(context.lang.twonlySafeRecoverBtn),
|
label: Text(context.lang.twonlySafeRecoverBtn),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import 'package:twonly/src/views/settings/subscription/voucher.view.dart';
|
||||||
// import 'package:url_launcher/url_launcher.dart';
|
// import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class RefundCreditsView extends StatefulWidget {
|
class RefundCreditsView extends StatefulWidget {
|
||||||
const RefundCreditsView({super.key, required this.formattedBalance});
|
const RefundCreditsView({required this.formattedBalance, super.key});
|
||||||
final String formattedBalance;
|
final String formattedBalance;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -19,7 +19,7 @@ class _RefundCreditsViewState extends State<RefundCreditsView> {
|
||||||
title: const Text('Refund Credits'),
|
title: const Text('Refund Credits'),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -32,13 +32,15 @@ class _RefundCreditsViewState extends State<RefundCreditsView> {
|
||||||
const SizedBox(height: 20), // Space between balance and options
|
const SizedBox(height: 20), // Space between balance and options
|
||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text("Create a Voucher"),
|
title: const Text('Create a Voucher'),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.push(context,
|
await Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return VoucherView();
|
return const VoucherView();
|
||||||
}));
|
}));
|
||||||
|
if (context.mounted) {
|
||||||
Navigator.pop(context, false);
|
Navigator.pop(context, false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// ListTile(
|
// ListTile(
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class _ContactUsState extends State<ContactUsView> {
|
||||||
|
|
||||||
final messageOnSuccess = TextMessage()
|
final messageOnSuccess = TextMessage()
|
||||||
..body = []
|
..body = []
|
||||||
..userId = Int64(0);
|
..userId = Int64();
|
||||||
|
|
||||||
final uploadRequest = UploadRequest(
|
final uploadRequest = UploadRequest(
|
||||||
messagesOnSuccess: [messageOnSuccess],
|
messagesOnSuccess: [messageOnSuccess],
|
||||||
|
|
@ -116,6 +116,7 @@ class _ContactUsState extends State<ContactUsView> {
|
||||||
'Debug Log: https://api.twonly.eu/api/download/$token';
|
'Debug Log: https://api.twonly.eu/api/download/$token';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!mounted) return '';
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Could not upload the debug log!')),
|
const SnackBar(content: Text('Could not upload the debug log!')),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:http/http.dart' as http;
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class SubmitMessage extends StatefulWidget {
|
class SubmitMessage extends StatefulWidget {
|
||||||
const SubmitMessage({super.key, required this.fullMessage});
|
const SubmitMessage({required this.fullMessage, super.key});
|
||||||
|
|
||||||
final String fullMessage;
|
final String fullMessage;
|
||||||
|
|
||||||
|
|
@ -22,14 +22,14 @@ class _ContactUsState extends State<SubmitMessage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _submitFeedback() async {
|
Future<void> _submitFeedback() async {
|
||||||
final String feedback = _controller.text;
|
final feedback = _controller.text;
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (feedback.isEmpty) {
|
if (feedback.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Please enter your message.')),
|
const SnackBar(content: Text('Please enter your message.')),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ class _ContactUsState extends State<SubmitMessage> {
|
||||||
} else {
|
} else {
|
||||||
// Handle error response
|
// Handle error response
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Failed to submit feedback.')),
|
const SnackBar(content: Text('Failed to submit feedback.')),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -69,30 +69,30 @@ class _ContactUsState extends State<SubmitMessage> {
|
||||||
title: Text(context.lang.settingsHelpContactUs),
|
title: Text(context.lang.settingsHelpContactUs),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
context.lang.contactUsLastWarning,
|
context.lang.contactUsLastWarning,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: context.lang.contactUsYourMessage,
|
hintText: context.lang.contactUsYourMessage,
|
||||||
border: OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
minLines: 5,
|
minLines: 5,
|
||||||
maxLines: 20,
|
maxLines: 20,
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 40, horizontal: 40),
|
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 40),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: (isLoading) ? null : _submitFeedback,
|
onPressed: isLoading ? null : _submitFeedback,
|
||||||
child: Text(context.lang.submit),
|
child: Text(context.lang.submit),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,18 @@ import 'package:twonly/src/views/camera/image_editor/layers/filters/location_fil
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class UrlListTitle extends StatelessWidget {
|
class UrlListTitle extends StatelessWidget {
|
||||||
|
const UrlListTitle({
|
||||||
|
required this.title,
|
||||||
|
required this.url,
|
||||||
|
super.key,
|
||||||
|
this.leading,
|
||||||
|
this.subtitle,
|
||||||
|
});
|
||||||
final String? title;
|
final String? title;
|
||||||
final String url;
|
final String url;
|
||||||
final String? subtitle;
|
final String? subtitle;
|
||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
|
|
||||||
const UrlListTitle({
|
|
||||||
super.key,
|
|
||||||
required this.title,
|
|
||||||
required this.url,
|
|
||||||
this.leading,
|
|
||||||
this.subtitle,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
|
@ -28,7 +27,7 @@ class UrlListTitle extends StatelessWidget {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
launchUrl(Uri.parse(url));
|
launchUrl(Uri.parse(url));
|
||||||
},
|
},
|
||||||
trailing: FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15),
|
trailing: const FaIcon(FontAwesomeIcons.arrowUpRightFromSquare, size: 15),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +49,7 @@ class _CreditsViewState extends State<CreditsView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
Future<void> initAsync() async {
|
||||||
sticker = (await getStickerIndex()).where((x) => x.source != "").toList();
|
sticker = (await getStickerIndex()).where((x) => x.source != '').toList();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,86 +61,86 @@ class _CreditsViewState extends State<CreditsView> {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "twonly Logo",
|
title: 'twonly Logo',
|
||||||
subtitle: "by Font Awesome (modified)",
|
subtitle: 'by Font Awesome (modified)',
|
||||||
url: "https://fontawesome.com/icons/link?f=classic&s=solid",
|
url: 'https://fontawesome.com/icons/link?f=classic&s=solid',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Most Icons",
|
title: 'Most Icons',
|
||||||
subtitle: "by Font Awesome",
|
subtitle: 'by Font Awesome',
|
||||||
url: "https://github.com/FortAwesome/Font-Awesome",
|
url: 'https://github.com/FortAwesome/Font-Awesome',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Animated Emoji",
|
title: 'Animated Emoji',
|
||||||
subtitle: "CC BY 4.0",
|
subtitle: 'CC BY 4.0',
|
||||||
url: "https://googlefonts.github.io/noto-emoji-animation/",
|
url: 'https://googlefonts.github.io/noto-emoji-animation/',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Avatar Icons",
|
title: 'Avatar Icons',
|
||||||
url: "https://github.com/RoadTripMoustache/avatar_maker",
|
url: 'https://github.com/RoadTripMoustache/avatar_maker',
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
ListTile(
|
const ListTile(
|
||||||
title: Center(
|
title: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Animations",
|
'Animations',
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "selfie fast Animation",
|
title: 'selfie fast Animation',
|
||||||
subtitle: "Brandon Ambuila",
|
subtitle: 'Brandon Ambuila',
|
||||||
url:
|
url:
|
||||||
"https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E",
|
'https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Security status - Safe Animation",
|
title: 'Security status - Safe Animation',
|
||||||
subtitle: "Yogesh Pal",
|
subtitle: 'Yogesh Pal',
|
||||||
url:
|
url:
|
||||||
"https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx",
|
'https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "send mail Animation",
|
title: 'send mail Animation',
|
||||||
subtitle: "jignesh gajjar",
|
subtitle: 'jignesh gajjar',
|
||||||
url: "https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq",
|
url: 'https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Present for you Animation",
|
title: 'Present for you Animation',
|
||||||
subtitle: "Tatsiana Melnikova",
|
subtitle: 'Tatsiana Melnikova',
|
||||||
url:
|
url:
|
||||||
"https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY",
|
'https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Take a photo Animation",
|
title: 'Take a photo Animation',
|
||||||
subtitle: "Nguyễn Như Lân",
|
subtitle: 'Nguyễn Như Lân',
|
||||||
url:
|
url:
|
||||||
"https://lottiefiles.com/free-animation/take-a-photo-CzOUerxwPP?color-palette=true",
|
'https://lottiefiles.com/free-animation/take-a-photo-CzOUerxwPP?color-palette=true',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Valentine's Day-Animation",
|
title: "Valentine's Day-Animation",
|
||||||
subtitle: "Strezha",
|
subtitle: 'Strezha',
|
||||||
url:
|
url:
|
||||||
"https://lottiefiles.com/de/free-animation/valentines-day-1UiMkPHnPK?color-palette=true",
|
'https://lottiefiles.com/de/free-animation/valentines-day-1UiMkPHnPK?color-palette=true',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "success-Animation",
|
title: 'success-Animation',
|
||||||
subtitle: "Aman Awasthy",
|
subtitle: 'Aman Awasthy',
|
||||||
url:
|
url:
|
||||||
"https://lottiefiles.com/de/free-animation/success-tick-cuwjLHAR7g",
|
'https://lottiefiles.com/de/free-animation/success-tick-cuwjLHAR7g',
|
||||||
),
|
),
|
||||||
UrlListTitle(
|
const UrlListTitle(
|
||||||
title: "Failed-Animation",
|
title: 'Failed-Animation',
|
||||||
subtitle: "Ahmed Shami أحمد شامي",
|
subtitle: 'Ahmed Shami أحمد شامي',
|
||||||
url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv",
|
url: 'https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv',
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
if (sticker.isNotEmpty)
|
if (sticker.isNotEmpty)
|
||||||
ListTile(
|
const ListTile(
|
||||||
title: Center(
|
title: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Filters",
|
'Filters',
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
...sticker.map(
|
...sticker.map(
|
||||||
|
|
@ -150,9 +149,9 @@ class _CreditsViewState extends State<CreditsView> {
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 50,
|
width: 50,
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: "https://twonly.eu/${x.imageSrc}"),
|
imageUrl: 'https://twonly.eu/${x.imageSrc}'),
|
||||||
),
|
),
|
||||||
title: "",
|
title: '',
|
||||||
url: x.source,
|
url: x.source,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
||||||
// Assuming the button is at the bottom of the scroll view
|
// Assuming the button is at the bottom of the scroll view
|
||||||
_scrollController.animateTo(
|
_scrollController.animateTo(
|
||||||
_scrollController.position.maxScrollExtent, // Scroll to the bottom
|
_scrollController.position.maxScrollExtent, // Scroll to the bottom
|
||||||
duration: Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -42,12 +42,12 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Text(logText),
|
child: Text(logText),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -65,7 +65,8 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
||||||
final result = await SharePlus.instance.share(params);
|
final result = await SharePlus.instance.share(params);
|
||||||
|
|
||||||
if (result.status != ShareResultStatus.success) {
|
if (result.status != ShareResultStatus.success) {
|
||||||
Clipboard.setData(ClipboardData(text: logText));
|
await Clipboard.setData(
|
||||||
|
ClipboardData(text: logText));
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class NotificationView extends StatelessWidget {
|
||||||
final pushData = await getPushData(
|
final pushData = await getPushData(
|
||||||
user.userId,
|
user.userId,
|
||||||
PushNotification(
|
PushNotification(
|
||||||
messageId: Int64(0),
|
messageId: Int64(),
|
||||||
kind: PushKind.testNotification,
|
kind: PushKind.testNotification,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class _PrivacyViewState extends State<PrivacyView> {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await Navigator.push(context,
|
await Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (context) {
|
MaterialPageRoute(builder: (context) {
|
||||||
return PrivacyViewBlockUsers();
|
return const PrivacyViewBlockUsers();
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pie_menu/pie_menu.dart';
|
import 'package:pie_menu/pie_menu.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||||
|
|
||||||
class PrivacyViewBlockUsers extends StatefulWidget {
|
class PrivacyViewBlockUsers extends StatefulWidget {
|
||||||
|
|
@ -18,7 +18,7 @@ class PrivacyViewBlockUsers extends StatefulWidget {
|
||||||
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
||||||
late Stream<List<Contact>> allUsers;
|
late Stream<List<Contact>> allUsers;
|
||||||
List<Contact> filteredUsers = [];
|
List<Contact> filteredUsers = [];
|
||||||
String filter = "";
|
String filter = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -40,11 +40,12 @@ class _PrivacyViewBlockUsers extends State<PrivacyViewBlockUsers> {
|
||||||
body: PieCanvas(
|
body: PieCanvas(
|
||||||
theme: getPieCanvasTheme(context),
|
theme: getPieCanvasTheme(context),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
|
padding:
|
||||||
|
const EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onChanged: (value) => setState(() {
|
onChanged: (value) => setState(() {
|
||||||
filter = value;
|
filter = value;
|
||||||
|
|
@ -110,7 +111,7 @@ class UserList extends StatelessWidget {
|
||||||
restorationId: 'new_message_users_list',
|
restorationId: 'new_message_users_list',
|
||||||
itemCount: users.length,
|
itemCount: users.length,
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
Contact user = users[i];
|
final user = users[i];
|
||||||
return UserContextMenuBlocked(
|
return UserContextMenuBlocked(
|
||||||
contact: user,
|
contact: user,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,10 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
|
|
||||||
Future<void> updateUserAvatar(String json, String svg) async {
|
Future<void> updateUserAvatar(String json, String svg) async {
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.avatarJson = json;
|
user
|
||||||
user.avatarSvg = svg;
|
..avatarJson = json
|
||||||
user.avatarCounter = user.avatarCounter + 1;
|
..avatarSvg = svg
|
||||||
|
..avatarCounter = user.avatarCounter + 1;
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
await notifyContactsAboutProfileChange();
|
await notifyContactsAboutProfileChange();
|
||||||
|
|
@ -35,7 +36,7 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) {
|
AvatarMakerThemeData getAvatarMakerTheme(BuildContext context) {
|
||||||
if (isDarkMode(context)) {
|
if (isDarkMode(context)) {
|
||||||
return AvatarMakerThemeData(
|
return AvatarMakerThemeData(
|
||||||
boxDecoration: BoxDecoration(
|
boxDecoration: const BoxDecoration(
|
||||||
boxShadow: [BoxShadow()],
|
boxShadow: [BoxShadow()],
|
||||||
),
|
),
|
||||||
unselectedTileDecoration: BoxDecoration(
|
unselectedTileDecoration: BoxDecoration(
|
||||||
|
|
@ -49,13 +50,13 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
selectedIconColor: Colors.white,
|
selectedIconColor: Colors.white,
|
||||||
unselectedIconColor: Colors.grey,
|
unselectedIconColor: Colors.grey,
|
||||||
primaryBgColor: Colors.black, // Dark mode background
|
primaryBgColor: Colors.black, // Dark mode background
|
||||||
secondaryBgColor: Colors.grey[850]!, // Dark mode secondary background
|
secondaryBgColor: Colors.grey[850], // Dark mode secondary background
|
||||||
labelTextStyle:
|
labelTextStyle:
|
||||||
TextStyle(color: Colors.white), // Light text for dark mode
|
const TextStyle(color: Colors.white), // Light text for dark mode
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return AvatarMakerThemeData(
|
return AvatarMakerThemeData(
|
||||||
boxDecoration: BoxDecoration(
|
boxDecoration: const BoxDecoration(
|
||||||
boxShadow: [BoxShadow()],
|
boxShadow: [BoxShadow()],
|
||||||
),
|
),
|
||||||
unselectedTileDecoration: BoxDecoration(
|
unselectedTileDecoration: BoxDecoration(
|
||||||
|
|
@ -69,9 +70,9 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
selectedIconColor: Colors.black,
|
selectedIconColor: Colors.black,
|
||||||
unselectedIconColor: Colors.grey,
|
unselectedIconColor: Colors.grey,
|
||||||
primaryBgColor: Colors.white, // Light mode background
|
primaryBgColor: Colors.white, // Light mode background
|
||||||
secondaryBgColor: Colors.grey[200]!, // Light mode secondary background
|
secondaryBgColor: Colors.grey[200], // Light mode secondary background
|
||||||
labelTextStyle:
|
labelTextStyle:
|
||||||
TextStyle(color: Colors.black), // Dark text for light mode
|
const TextStyle(color: Colors.black), // Dark text for light mode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,10 +86,9 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
body: Center(
|
body: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 00),
|
padding: EdgeInsets.zero,
|
||||||
child: AvatarMakerAvatar(
|
child: AvatarMakerAvatar(
|
||||||
radius: 130,
|
radius: 130,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
|
|
@ -100,7 +100,7 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.floppyDisk),
|
icon: const FaIcon(FontAwesomeIcons.floppyDisk),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _avatarMakerController.saveAvatarSVG();
|
await _avatarMakerController.saveAvatarSVG();
|
||||||
final json =
|
final json =
|
||||||
|
|
@ -113,27 +113,23 @@ class _ModifyAvatarState extends State<ModifyAvatar> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.shuffle),
|
icon: const FaIcon(FontAwesomeIcons.shuffle),
|
||||||
onPressed: () {
|
onPressed:
|
||||||
_avatarMakerController.randomizedSelectedOptions();
|
_avatarMakerController.randomizedSelectedOptions,
|
||||||
},
|
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(FontAwesomeIcons.rotateLeft),
|
icon: const Icon(FontAwesomeIcons.rotateLeft),
|
||||||
onPressed: () {
|
onPressed: _avatarMakerController.restoreState,
|
||||||
_avatarMakerController.restoreState();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 30),
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 30),
|
||||||
child: AvatarMakerCustomizer(
|
child: AvatarMakerCustomizer(
|
||||||
scaffoldWidth:
|
scaffoldWidth:
|
||||||
min(600, MediaQuery.of(context).size.width * 0.85),
|
min(600, MediaQuery.of(context).size.width * 0.85),
|
||||||
autosave: false,
|
|
||||||
theme: getAvatarMakerTheme(context),
|
theme: getAvatarMakerTheme(context),
|
||||||
controller: _avatarMakerController,
|
controller: _avatarMakerController,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:avatar_maker/avatar_maker.dart';
|
import 'package:avatar_maker/avatar_maker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/views/components/better_list_title.dart';
|
|
||||||
import 'package:twonly/src/model/json/userdata.dart';
|
import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||||
import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart';
|
import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart';
|
||||||
|
|
||||||
class ProfileView extends StatefulWidget {
|
class ProfileView extends StatefulWidget {
|
||||||
|
|
@ -33,13 +33,14 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
|
|
||||||
Future<void> updateUserDisplayName(String displayName) async {
|
Future<void> updateUserDisplayName(String displayName) async {
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.displayName = displayName;
|
user
|
||||||
user.avatarCounter = user.avatarCounter + 1;
|
..displayName = displayName
|
||||||
|
..avatarCounter = user.avatarCounter + 1;
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
|
|
||||||
await notifyContactsAboutProfileChange();
|
await notifyContactsAboutProfileChange();
|
||||||
initAsync();
|
await initAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -49,34 +50,34 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
title: Text(context.lang.settingsProfile),
|
title: Text(context.lang.settingsProfile),
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
physics: BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
AvatarMakerAvatar(
|
AvatarMakerAvatar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
radius: 80,
|
radius: 80,
|
||||||
controller: _avatarMakerController,
|
controller: _avatarMakerController,
|
||||||
),
|
),
|
||||||
SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Center(
|
Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 35,
|
height: 35,
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
icon: Icon(Icons.edit),
|
icon: const Icon(Icons.edit),
|
||||||
label: Text(context.lang.settingsProfileCustomizeAvatar),
|
label: Text(context.lang.settingsProfileCustomizeAvatar),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => ModifyAvatar(),
|
builder: (context) => const ModifyAvatar(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_avatarMakerController.performRestore();
|
await _avatarMakerController.performRestore();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
icon: FontAwesomeIcons.userPen,
|
icon: FontAwesomeIcons.userPen,
|
||||||
|
|
@ -85,8 +86,8 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final displayName =
|
final displayName =
|
||||||
await showDisplayNameChangeDialog(context, user!.displayName);
|
await showDisplayNameChangeDialog(context, user!.displayName);
|
||||||
if (context.mounted && displayName != null && displayName != "") {
|
if (context.mounted && displayName != null && displayName != '') {
|
||||||
updateUserDisplayName(displayName);
|
await updateUserDisplayName(displayName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -98,8 +99,7 @@ class _ProfileViewState extends State<ProfileView> {
|
||||||
|
|
||||||
Future<String?> showDisplayNameChangeDialog(
|
Future<String?> showDisplayNameChangeDialog(
|
||||||
BuildContext context, String currentName) {
|
BuildContext context, String currentName) {
|
||||||
final TextEditingController controller =
|
final controller = TextEditingController(text: currentName);
|
||||||
TextEditingController(text: currentName);
|
|
||||||
|
|
||||||
return showDialog<String>(
|
return showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class _SettingsMainViewState extends State<SettingsMainView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ContactAvatar(
|
ContactAvatar(
|
||||||
userData: userData!,
|
userData: userData,
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
),
|
),
|
||||||
Container(width: 20, color: Colors.transparent),
|
Container(width: 20, color: Colors.transparent),
|
||||||
|
|
@ -200,7 +200,7 @@ class _SettingsMainViewState extends State<SettingsMainView> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
Navigator.push(context, MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return HelpView();
|
return const HelpView();
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ class _ShareWithFriendsView extends State<ShareWithFriendsView> {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_controller.text =
|
_controller.text =
|
||||||
context.lang.inviteFriendsShareText("https://twonly.eu/install");
|
context.lang.inviteFriendsShareText('https://twonly.eu/install');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _shareText() async {
|
Future<void> _shareText() async {
|
||||||
final textToShare = _controller.text;
|
final textToShare = _controller.text;
|
||||||
final params = ShareParams(
|
final params = ShareParams(
|
||||||
text: textToShare,
|
text: textToShare,
|
||||||
|
|
@ -37,19 +37,18 @@ class _ShareWithFriendsView extends State<ShareWithFriendsView> {
|
||||||
title: Text(context.lang.inviteFriends),
|
title: Text(context.lang.inviteFriends),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
style: const TextStyle(fontSize: 14.0),
|
style: const TextStyle(fontSize: 14),
|
||||||
decoration: InputDecoration(),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.0),
|
const SizedBox(height: 16),
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: _shareText,
|
onPressed: _shareText,
|
||||||
icon: FaIcon(FontAwesomeIcons.shareFromSquare),
|
icon: const FaIcon(FontAwesomeIcons.shareFromSquare),
|
||||||
label: Text(context.lang.inviteFriendsShareBtn),
|
label: Text(context.lang.inviteFriendsShareBtn),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ class _AdditionalUsersViewState extends State<AdditionalUsersView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ class _ManageSubscriptionViewState extends State<ManageSubscriptionView> {
|
||||||
),
|
),
|
||||||
onTap: toggleRenewalOption,
|
onTap: toggleRenewalOption,
|
||||||
trailing: Checkbox(
|
trailing: Checkbox(
|
||||||
value: autoRenewal!,
|
value: autoRenewal,
|
||||||
onChanged: (a) {
|
onChanged: (a) {
|
||||||
toggleRenewalOption();
|
toggleRenewalOption();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
PlanCard(
|
PlanCard(
|
||||||
planId: 'Tester',
|
planId: 'Tester',
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
bool activate = await showAlertDialog(
|
final activate = await showAlertDialog(
|
||||||
context,
|
context,
|
||||||
context.lang.testingAccountTitle,
|
context.lang.testingAccountTitle,
|
||||||
context.lang.testingAccountBody,
|
context.lang.testingAccountBody,
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class TransactionView extends StatefulWidget {
|
class TransactionView extends StatefulWidget {
|
||||||
const TransactionView(
|
const TransactionView({
|
||||||
{super.key, required this.transactions, required this.formattedBalance});
|
required this.transactions,
|
||||||
|
required this.formattedBalance,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
final List<Response_Transaction>? transactions;
|
final List<Response_Transaction>? transactions;
|
||||||
final String formattedBalance;
|
final String formattedBalance;
|
||||||
|
|
||||||
|
|
@ -23,7 +26,7 @@ class _TransactionViewState extends State<TransactionView> {
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(32.0),
|
padding: const EdgeInsets.all(32),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -51,10 +54,9 @@ class _TransactionViewState extends State<TransactionView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransactionCard extends StatefulWidget {
|
class TransactionCard extends StatefulWidget {
|
||||||
|
const TransactionCard({required this.transaction, super.key});
|
||||||
final Response_Transaction transaction;
|
final Response_Transaction transaction;
|
||||||
|
|
||||||
const TransactionCard({super.key, required this.transaction});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TransactionCard> createState() => _TransactionCardState();
|
State<TransactionCard> createState() => _TransactionCardState();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ class _VoucherCardState extends State<VoucherCard> {
|
||||||
margin: const EdgeInsets.all(10),
|
margin: const EdgeInsets.all(10),
|
||||||
elevation: 5,
|
elevation: 5,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -160,7 +160,7 @@ Future<void> redeemVoucher(BuildContext context) async {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
// Convert to uppercase
|
// Convert to uppercase
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@ TargetFocus getTargetFocus(
|
||||||
left: 0, right: 0, top: top, bottom: bottom),
|
left: 0, right: 0, top: top, bottom: bottom),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,18 @@ Future<void> showChatListTutorialSearchOtherUsers(
|
||||||
GlobalKey searchForOtherUsers,
|
GlobalKey searchForOtherUsers,
|
||||||
) async {
|
) async {
|
||||||
await lockDisplayTutorial.protect(() async {
|
await lockDisplayTutorial.protect(() async {
|
||||||
if (await checkIfTutorialAlreadyShown("chat_list:search_users")) {
|
if (await checkIfTutorialAlreadyShown('chat_list:search_users')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
List<TargetFocus> targets = [];
|
final targets = <TargetFocus>[
|
||||||
targets.add(getTargetFocus(
|
getTargetFocus(
|
||||||
context,
|
context,
|
||||||
searchForOtherUsers,
|
searchForOtherUsers,
|
||||||
context.lang.tutorialChatListSearchUsersTitle,
|
context.lang.tutorialChatListSearchUsersTitle,
|
||||||
context.lang.tutorialChatListSearchUsersDesc,
|
context.lang.tutorialChatListSearchUsersDesc,
|
||||||
));
|
)
|
||||||
|
];
|
||||||
await showTutorial(context, targets);
|
await showTutorial(context, targets);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -31,17 +32,18 @@ Future<void> showChatListTutorialContextMenu(
|
||||||
GlobalKey firstUserListItemKey,
|
GlobalKey firstUserListItemKey,
|
||||||
) async {
|
) async {
|
||||||
await lockDisplayTutorial.protect(() async {
|
await lockDisplayTutorial.protect(() async {
|
||||||
if (await checkIfTutorialAlreadyShown("chat_list:context_menu")) {
|
if (await checkIfTutorialAlreadyShown('chat_list:context_menu')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
List<TargetFocus> targets = [];
|
final targets = <TargetFocus>[
|
||||||
targets.add(getTargetFocus(
|
getTargetFocus(
|
||||||
context,
|
context,
|
||||||
firstUserListItemKey,
|
firstUserListItemKey,
|
||||||
context.lang.tutorialChatListContextMenuTitle,
|
context.lang.tutorialChatListContextMenuTitle,
|
||||||
context.lang.tutorialChatListContextMenuDesc,
|
context.lang.tutorialChatListContextMenuDesc,
|
||||||
));
|
)
|
||||||
|
];
|
||||||
await showTutorial(context, targets);
|
await showTutorial(context, targets);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -51,17 +53,18 @@ Future<void> showVerifyShieldTutorial(
|
||||||
GlobalKey firstUserListItemKey,
|
GlobalKey firstUserListItemKey,
|
||||||
) async {
|
) async {
|
||||||
await lockDisplayTutorial.protect(() async {
|
await lockDisplayTutorial.protect(() async {
|
||||||
if (await checkIfTutorialAlreadyShown("chat_messages:verify_shield")) {
|
if (await checkIfTutorialAlreadyShown('chat_messages:verify_shield')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
List<TargetFocus> targets = [];
|
final targets = <TargetFocus>[
|
||||||
targets.add(getTargetFocus(
|
getTargetFocus(
|
||||||
context,
|
context,
|
||||||
firstUserListItemKey,
|
firstUserListItemKey,
|
||||||
context.lang.tutorialChatMessagesVerifyShieldTitle,
|
context.lang.tutorialChatMessagesVerifyShieldTitle,
|
||||||
context.lang.tutorialChatMessagesVerifyShieldDesc,
|
context.lang.tutorialChatMessagesVerifyShieldDesc,
|
||||||
));
|
)
|
||||||
|
];
|
||||||
await showTutorial(context, targets);
|
await showTutorial(context, targets);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -71,17 +74,18 @@ Future<void> showReopenMediaFilesTutorial(
|
||||||
GlobalKey firstUserListItemKey,
|
GlobalKey firstUserListItemKey,
|
||||||
) async {
|
) async {
|
||||||
await lockDisplayTutorial.protect(() async {
|
await lockDisplayTutorial.protect(() async {
|
||||||
if (await checkIfTutorialAlreadyShown("chat_messages:reopen_message")) {
|
if (await checkIfTutorialAlreadyShown('chat_messages:reopen_message')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
List<TargetFocus> targets = [];
|
final targets = <TargetFocus>[
|
||||||
targets.add(getTargetFocus(
|
getTargetFocus(
|
||||||
context,
|
context,
|
||||||
firstUserListItemKey,
|
firstUserListItemKey,
|
||||||
context.lang.tutorialChatMessagesReopenMessageTitle,
|
context.lang.tutorialChatMessagesReopenMessageTitle,
|
||||||
context.lang.tutorialChatMessagesReopenMessageDesc,
|
context.lang.tutorialChatMessagesReopenMessageDesc,
|
||||||
));
|
)
|
||||||
|
];
|
||||||
await showTutorial(context, targets);
|
await showTutorial(context, targets);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
51
pubspec.yaml
51
pubspec.yaml
|
|
@ -10,12 +10,20 @@ environment:
|
||||||
sdk: ^3.6.0
|
sdk: ^3.6.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
avatar_maker: ^0.4.0
|
||||||
|
background_downloader: ^9.2.2
|
||||||
|
cached_network_image: ^3.4.1
|
||||||
|
camera: ^0.11.1
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
connectivity_plus: ^6.1.2
|
connectivity_plus: ^6.1.2
|
||||||
|
cryptography_flutter_plus: ^2.3.4
|
||||||
|
cryptography_plus: ^2.7.0
|
||||||
|
device_info_plus: ^11.5.0
|
||||||
drift: ^2.25.1
|
drift: ^2.25.1
|
||||||
drift_flutter: ^0.2.4
|
drift_flutter: ^0.2.4
|
||||||
firebase_core: ^3.11.0
|
firebase_core: ^3.11.0
|
||||||
firebase_messaging: ^15.2.2
|
firebase_messaging: ^15.2.2
|
||||||
|
fixnum: ^1.1.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_image_compress: ^2.4.0
|
flutter_image_compress: ^2.4.0
|
||||||
|
|
@ -25,15 +33,17 @@ dependencies:
|
||||||
# flutter_secure_storage: ^10.0.0-beta.4
|
# flutter_secure_storage: ^10.0.0-beta.4
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
path: ./dependencies/flutter_secure_storage/flutter_secure_storage
|
path: ./dependencies/flutter_secure_storage/flutter_secure_storage
|
||||||
|
flutter_svg: ^2.0.17
|
||||||
flutter_zxing:
|
flutter_zxing:
|
||||||
path: ./dependencies/flutter_zxing
|
path: ./dependencies/flutter_zxing
|
||||||
# pie_menu: ^3.2.7
|
|
||||||
pie_menu:
|
|
||||||
path: ./dependencies/flutter-pie-menu
|
|
||||||
font_awesome_flutter: ^10.8.0
|
font_awesome_flutter: ^10.8.0
|
||||||
gal: ^2.3.1
|
gal: ^2.3.1
|
||||||
|
get: ^4.7.2
|
||||||
hand_signature: ^3.0.3
|
hand_signature: ^3.0.3
|
||||||
|
hashlib: ^2.0.0
|
||||||
|
http: ^1.3.0
|
||||||
image: ^4.3.0
|
image: ^4.3.0
|
||||||
|
image_picker: ^1.1.2
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
introduction_screen: ^3.1.14
|
introduction_screen: ^3.1.14
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
|
|
@ -41,46 +51,35 @@ dependencies:
|
||||||
local_auth: ^2.3.0
|
local_auth: ^2.3.0
|
||||||
logging: ^1.3.0
|
logging: ^1.3.0
|
||||||
lottie: ^3.3.1
|
lottie: ^3.3.1
|
||||||
|
mutex: ^3.1.0
|
||||||
no_screenshot: ^0.3.1
|
no_screenshot: ^0.3.1
|
||||||
package_info_plus: ^8.2.1
|
package_info_plus: ^8.2.1
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
permission_handler: ^12.0.0+1
|
permission_handler: ^12.0.0+1
|
||||||
|
photo_view: ^0.15.0
|
||||||
|
pie_menu:
|
||||||
|
path: ./dependencies/flutter-pie-menu
|
||||||
protobuf: ^4.0.0
|
protobuf: ^4.0.0
|
||||||
cryptography_plus: ^2.7.0
|
|
||||||
provider: ^6.1.2
|
provider: ^6.1.2
|
||||||
restart_app: ^1.3.2
|
restart_app: ^1.3.2
|
||||||
screenshot: ^3.0.0
|
screenshot: ^3.0.0
|
||||||
url_launcher: ^6.3.1
|
|
||||||
web_socket_channel: ^3.0.1
|
|
||||||
camera: ^0.11.1
|
|
||||||
avatar_maker: ^0.4.0
|
|
||||||
flutter_svg: ^2.0.17
|
|
||||||
fixnum: ^1.1.1
|
|
||||||
mutex: ^3.1.0
|
|
||||||
cached_network_image: ^3.4.1
|
|
||||||
image_picker: ^1.1.2
|
|
||||||
http: ^1.3.0
|
|
||||||
get: ^4.7.2
|
|
||||||
video_player: ^2.9.5
|
|
||||||
video_compress: ^3.1.4
|
|
||||||
share_plus: ^11.0.0
|
share_plus: ^11.0.0
|
||||||
photo_view: ^0.15.0
|
|
||||||
tutorial_coach_mark: ^1.3.0
|
tutorial_coach_mark: ^1.3.0
|
||||||
background_downloader: ^9.2.2
|
url_launcher: ^6.3.1
|
||||||
hashlib: ^2.0.0
|
video_compress: ^3.1.4
|
||||||
|
video_player: ^2.9.5
|
||||||
video_thumbnail: ^0.5.6
|
video_thumbnail: ^0.5.6
|
||||||
device_info_plus: ^11.5.0
|
web_socket_channel: ^3.0.1
|
||||||
cryptography_flutter_plus: ^2.3.4
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
build_runner: ^2.4.15
|
||||||
|
drift_dev: ^2.25.2
|
||||||
|
flutter_launcher_icons: ^0.14.1
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: ^2.4.15
|
|
||||||
json_serializable: ^6.8.0
|
json_serializable: ^6.8.0
|
||||||
flutter_lints: ^6.0.0
|
|
||||||
flutter_launcher_icons: ^0.14.1
|
|
||||||
drift_dev: ^2.25.2
|
|
||||||
very_good_analysis: ^9.0.0
|
very_good_analysis: ^9.0.0
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
void main() {
|
void main() {
|
||||||
group('isEmoji', () {
|
group('isEmoji', () {
|
||||||
test('test isEmoji function', () {
|
test('test isEmoji function', () {
|
||||||
expect(isEmoji("Hallo"), false);
|
expect(isEmoji('Hallo'), false);
|
||||||
expect(isEmoji("😂"), true);
|
expect(isEmoji('😂'), true);
|
||||||
expect(isEmoji("😂😂"), false);
|
expect(isEmoji('😂😂'), false);
|
||||||
expect(isEmoji("Hallo 😂"), false);
|
expect(isEmoji('Hallo 😂'), false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue