diff --git a/integration_test/api_service.test.dart b/integration_test/api_service.test.dart index 58c3cfde..3b65146f 100644 --- a/integration_test/api_service.test.dart +++ b/integration_test/api_service.test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:twonly/core/frb_generated.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/services/background/callback_dispatcher.background.dart'; void main() { diff --git a/lib/app.dart b/lib/app.dart index b6f75f9f..637fa8c2 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,24 +1,26 @@ import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/providers/purchases.provider.dart'; import 'package:twonly/src/providers/routing.provider.dart'; import 'package:twonly/src/providers/settings.provider.dart'; import 'package:twonly/src/services/subscription.service.dart'; -import 'package:twonly/src/themes/dark.dart'; -import 'package:twonly/src/themes/light.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/pow.dart'; -import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/components/app_outdated.dart'; -import 'package:twonly/src/views/home.view.dart'; -import 'package:twonly/src/views/onboarding/onboarding.view.dart'; -import 'package:twonly/src/views/onboarding/register.view.dart'; -import 'package:twonly/src/views/settings/backup/setup_backup.view.dart'; -import 'package:twonly/src/views/unlock_twonly.view.dart'; +import 'package:twonly/src/visual/components/app_outdated.comp.dart'; +import 'package:twonly/src/visual/themes/dark.dart'; +import 'package:twonly/src/visual/themes/light.dart'; +import 'package:twonly/src/visual/views/home.view.dart'; +import 'package:twonly/src/visual/views/onboarding/onboarding.view.dart'; +import 'package:twonly/src/visual/views/onboarding/register.view.dart'; +import 'package:twonly/src/visual/views/settings/backup/backup_setup.view.dart'; +import 'package:twonly/src/visual/views/unlock_twonly.view.dart'; class App extends StatefulWidget { const App({super.key}); @@ -131,9 +133,9 @@ class _AppMainWidgetState extends State { if (_isUserCreated) { if (_isTwonlyLocked) { // do not change in case twonly was already unlocked at some point - _isTwonlyLocked = AppSession.currentUser.screenLockEnabled; + _isTwonlyLocked = appSession.currentUser.screenLockEnabled; } - if (AppSession.currentUser.appVersion < 62) { + if (appSession.currentUser.appVersion < 62) { _showDatabaseMigration = true; } } @@ -176,7 +178,7 @@ class _AppMainWidgetState extends State { _isTwonlyLocked = false; }), ); - } else if (AppSession.currentUser.twonlySafeBackup == null && + } else if (appSession.currentUser.twonlySafeBackup == null && !_skipBackup) { child = SetupBackupView( callBack: () => setState(() { @@ -204,7 +206,7 @@ class _AppMainWidgetState extends State { return Stack( children: [ child, - const AppOutdated(), + const AppOutdatedComp(), ], ); } diff --git a/lib/globals.dart b/lib/globals.dart index e7708ea3..2aa6ca00 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -3,9 +3,6 @@ import 'dart:async'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/userdata.dart'; -import 'package:twonly/src/services/api.service.dart'; class AppEnvironment { static late final String cacheDir; @@ -29,17 +26,3 @@ class AppState { class AppGlobalKeys { static final scaffoldMessengerKey = GlobalKey(); } - -class AppSession { - static late UserData currentUser; - - static final _userDataUpdateController = StreamController.broadcast(); - static Stream get onUserUpdated => _userDataUpdateController.stream; - - static void triggerUserUpdate() { - _userDataUpdateController.add(null); - } -} - -late ApiService apiService; -late TwonlyDB twonlyDB; diff --git a/lib/locator.dart b/lib/locator.dart new file mode 100644 index 00000000..36879ae6 --- /dev/null +++ b/lib/locator.dart @@ -0,0 +1,17 @@ +import 'package:get_it/get_it.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/services/api.service.dart'; +import 'package:twonly/src/services/user.service.dart'; + +final GetIt locator = GetIt.instance; + +void setupLocator() { + locator + ..registerLazySingleton(UserService.new) + ..registerLazySingleton(ApiService.new) + ..registerLazySingleton(TwonlyDB.new); +} + +UserService get appSession => locator(); +ApiService get apiService => locator(); +TwonlyDB get twonlyDB => locator(); diff --git a/lib/main.dart b/lib/main.dart index 451c48b2..91e2d502 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,21 +10,21 @@ import 'package:twonly/app.dart'; import 'package:twonly/core/bridge.dart' as bridge; import 'package:twonly/core/frb_generated.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/callbacks/callbacks.dart'; -import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/providers/image_editor.provider.dart'; import 'package:twonly/src/providers/purchases.provider.dart'; import 'package:twonly/src/providers/settings.provider.dart'; -import 'package:twonly/src/services/api.service.dart'; -import 'package:twonly/src/services/api/mediafiles/download.service.dart'; -import 'package:twonly/src/services/api/mediafiles/media_background.service.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/download.api.dart'; +import 'package:twonly/src/services/api/mediafiles/media_background.api.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/background/callback_dispatcher.background.dart'; import 'package:twonly/src/services/backup/create.backup.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/fcm.notifications.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/storage.dart'; @@ -34,6 +34,7 @@ void main() async { await AppEnvironment.init(); Log.init(); + setupLocator(); await RustLib.init(); @@ -60,7 +61,7 @@ void main() async { } if (user != null) { - AppSession.currentUser = user; + appSession.currentUser = user; if (user.allowErrorTrackingViaSentry) { AppState.allowErrorTrackingViaSentry = true; @@ -87,18 +88,15 @@ void main() async { unawaited(setupPushNotification()); - apiService = ApiService(); - twonlyDB = TwonlyDB(); - if (user != null) { - if (AppSession.currentUser.appVersion < 90) { + if (appSession.currentUser.appVersion < 90) { // BUG: Requested media files for reupload where not reuploaded because the wrong state... await twonlyDB.mediaFilesDao.updateAllRetransmissionUploadingState(); await updateUser((u) { u.appVersion = 90; }); } - if (AppSession.currentUser.appVersion < 91) { + if (appSession.currentUser.appVersion < 91) { // BUG: Requested media files for reupload where not reuploaded because the wrong state... await makeMigrationToVersion91(); await updateUser((u) { diff --git a/lib/src/callbacks/user_discovery.callbacks.dart b/lib/src/callbacks/user_discovery.callbacks.dart index bab169d2..0f827100 100644 --- a/lib/src/callbacks/user_discovery.callbacks.dart +++ b/lib/src/callbacks/user_discovery.callbacks.dart @@ -5,7 +5,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart' // ignore: implementation_imports import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart'; import 'package:twonly/core/bridge.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; diff --git a/lib/src/constants/secure_storage_keys.dart b/lib/src/constants/secure_storage.keys.dart similarity index 100% rename from lib/src/constants/secure_storage_keys.dart rename to lib/src/constants/secure_storage.keys.dart diff --git a/lib/src/database/daos/contacts.dao.dart b/lib/src/database/daos/contacts.dao.dart index 9b34b3ef..7acf9e1c 100644 --- a/lib/src/database/daos/contacts.dao.dart +++ b/lib/src/database/daos/contacts.dao.dart @@ -1,5 +1,5 @@ import 'package:drift/drift.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; @@ -140,7 +140,7 @@ class ContactsDao extends DatabaseAccessor with _$ContactsDaoMixin { t.userDiscoveryVersion.isNotNull() & t.userDiscoveryExcluded.equals(false) & t.mediaSendCounter.isBiggerOrEqualValue( - AppSession.currentUser.minimumRequiredImagesExchanged, + appSession.currentUser.minimumRequiredImagesExchanged, ), )) .watch(); diff --git a/lib/src/database/daos/groups.dao.dart b/lib/src/database/daos/groups.dao.dart index a66ccfcd..0092be9d 100644 --- a/lib/src/database/daos/groups.dao.dart +++ b/lib/src/database/daos/groups.dao.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; import 'package:hashlib/random.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/flame.service.dart'; @@ -113,7 +113,7 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { int contactId, GroupsCompanion group, ) async { - final groupIdDirectChat = getUUIDforDirectChat(contactId, AppSession.currentUser.userId); + final groupIdDirectChat = getUUIDforDirectChat(contactId, appSession.currentUser.userId); final insertGroup = group.copyWith( groupId: Value(groupIdDirectChat), isDirectChat: const Value(true), @@ -209,7 +209,7 @@ class GroupsDao extends DatabaseAccessor with _$GroupsDaoMixin { } Stream watchDirectChat(int contactId) { - final groupId = getUUIDforDirectChat(contactId, AppSession.currentUser.userId); + final groupId = getUUIDforDirectChat(contactId, appSession.currentUser.userId); return (select( groups, )..where((t) => t.groupId.equals(groupId))).watchSingleOrNull(); diff --git a/lib/src/database/daos/messages.dao.dart b/lib/src/database/daos/messages.dao.dart index b056ff4b..0f67400d 100644 --- a/lib/src/database/daos/messages.dao.dart +++ b/lib/src/database/daos/messages.dao.dart @@ -1,7 +1,7 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:hashlib/random.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; diff --git a/lib/src/database/daos/reactions.dao.dart b/lib/src/database/daos/reactions.dao.dart index a5819908..bd1405b3 100644 --- a/lib/src/database/daos/reactions.dao.dart +++ b/lib/src/database/daos/reactions.dao.dart @@ -1,10 +1,10 @@ import 'package:drift/drift.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/reactions.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/views/components/animate_icon.dart'; +import 'package:twonly/src/visual/components/animate_icon.comp.dart'; part 'reactions.dao.g.dart'; diff --git a/lib/src/database/daos/receipts.dao.dart b/lib/src/database/daos/receipts.dao.dart index 34b9a7a7..b999d488 100644 --- a/lib/src/database/daos/receipts.dao.dart +++ b/lib/src/database/daos/receipts.dao.dart @@ -5,7 +5,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/tables/receipts.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/utils/log.dart'; part 'receipts.dao.g.dart'; diff --git a/lib/src/database/signal/connect_identity_key_store.dart b/lib/src/database/signal/signal_identity_key_store.dart similarity index 93% rename from lib/src/database/signal/connect_identity_key_store.dart rename to lib/src/database/signal/signal_identity_key_store.dart index 2fa8ce38..ed917656 100644 --- a/lib/src/database/signal/connect_identity_key_store.dart +++ b/lib/src/database/signal/signal_identity_key_store.dart @@ -1,11 +1,11 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; -class ConnectIdentityKeyStore extends IdentityKeyStore { - ConnectIdentityKeyStore(this.identityKeyPair, this.localRegistrationId); +class SignalIdentityKeyStore extends IdentityKeyStore { + SignalIdentityKeyStore(this.identityKeyPair, this.localRegistrationId); final IdentityKeyPair identityKeyPair; final int localRegistrationId; diff --git a/lib/src/database/signal/connect_pre_key_store.dart b/lib/src/database/signal/signal_pre_key_store.dart similarity index 94% rename from lib/src/database/signal/connect_pre_key_store.dart rename to lib/src/database/signal/signal_pre_key_store.dart index 2a49b926..10ae75c7 100644 --- a/lib/src/database/signal/connect_pre_key_store.dart +++ b/lib/src/database/signal/signal_pre_key_store.dart @@ -1,10 +1,10 @@ import 'package:drift/drift.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/log.dart'; -class ConnectPreKeyStore extends PreKeyStore { +class SignalPreKeyStore extends PreKeyStore { @override Future containsPreKey(int preKeyId) async { final preKeyRecord = await (twonlyDB.select( diff --git a/lib/src/database/signal/connect_signal_protocol_store.dart b/lib/src/database/signal/signal_protocol_store.dart similarity index 83% rename from lib/src/database/signal/connect_signal_protocol_store.dart rename to lib/src/database/signal/signal_protocol_store.dart index ea07739f..0c517037 100644 --- a/lib/src/database/signal/connect_signal_protocol_store.dart +++ b/lib/src/database/signal/signal_protocol_store.dart @@ -1,23 +1,23 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/src/database/signal/connect_identity_key_store.dart'; -import 'package:twonly/src/database/signal/connect_pre_key_store.dart'; -import 'package:twonly/src/database/signal/connect_session_store.dart'; -import 'package:twonly/src/database/signal/connect_signed_pre_key_store.dart'; +import 'package:twonly/src/database/signal/signal_identity_key_store.dart'; +import 'package:twonly/src/database/signal/signal_pre_key_store.dart'; +import 'package:twonly/src/database/signal/signal_session_store.dart'; +import 'package:twonly/src/database/signal/signal_signed_pre_key_store.dart'; -class ConnectSignalProtocolStore implements SignalProtocolStore { - ConnectSignalProtocolStore( +class SignalSignalProtocolStore implements SignalProtocolStore { + SignalSignalProtocolStore( IdentityKeyPair identityKeyPair, int registrationId, ) { - _identityKeyStore = ConnectIdentityKeyStore( + _identityKeyStore = SignalIdentityKeyStore( identityKeyPair, registrationId, ); } - final preKeyStore = ConnectPreKeyStore(); - final sessionStore = ConnectSessionStore(); - final signedPreKeyStore = ConnectSignedPreKeyStore(); + final preKeyStore = SignalPreKeyStore(); + final sessionStore = SignalSessionStore(); + final signedPreKeyStore = SignalSignedPreKeyStore(); late IdentityKeyStore _identityKeyStore; diff --git a/lib/src/database/signal/connect_sender_key_store.dart b/lib/src/database/signal/signal_sender_key_store.dart similarity index 91% rename from lib/src/database/signal/connect_sender_key_store.dart rename to lib/src/database/signal/signal_sender_key_store.dart index c637f880..04e082bc 100644 --- a/lib/src/database/signal/connect_sender_key_store.dart +++ b/lib/src/database/signal/signal_sender_key_store.dart @@ -1,9 +1,9 @@ import 'package:drift/drift.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; -class ConnectSenderKeyStore extends SenderKeyStore { +class SignalSenderKeyStore extends SenderKeyStore { @override Future loadSenderKey(SenderKeyName senderKeyName) async { final identity = diff --git a/lib/src/database/signal/connect_session_store.dart b/lib/src/database/signal/signal_session_store.dart similarity index 96% rename from lib/src/database/signal/connect_session_store.dart rename to lib/src/database/signal/signal_session_store.dart index 92208e39..fce145e4 100644 --- a/lib/src/database/signal/connect_session_store.dart +++ b/lib/src/database/signal/signal_session_store.dart @@ -1,9 +1,9 @@ import 'package:drift/drift.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; -class ConnectSessionStore extends SessionStore { +class SignalSessionStore extends SessionStore { @override Future containsSession(SignalProtocolAddress address) async { final sessions = diff --git a/lib/src/database/signal/connect_signed_pre_key_store.dart b/lib/src/database/signal/signal_signed_pre_key_store.dart similarity index 95% rename from lib/src/database/signal/connect_signed_pre_key_store.dart rename to lib/src/database/signal/signal_signed_pre_key_store.dart index 91dcc5d0..4e949e47 100644 --- a/lib/src/database/signal/connect_signed_pre_key_store.dart +++ b/lib/src/database/signal/signal_signed_pre_key_store.dart @@ -3,9 +3,9 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; -class ConnectSignedPreKeyStore extends SignedPreKeyStore { +class SignalSignedPreKeyStore extends SignedPreKeyStore { Future> getStore() async { const storage = FlutterSecureStorage(); final storeSerialized = await storage.read( diff --git a/lib/src/model/json/signal_identity.dart b/lib/src/model/json/signal_identity.model.dart similarity index 95% rename from lib/src/model/json/signal_identity.dart rename to lib/src/model/json/signal_identity.model.dart index d4558877..bc01d334 100644 --- a/lib/src/model/json/signal_identity.dart +++ b/lib/src/model/json/signal_identity.model.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:json_annotation/json_annotation.dart'; -part 'signal_identity.g.dart'; +part 'signal_identity.model.g.dart'; @JsonSerializable() class SignalIdentity { diff --git a/lib/src/model/json/signal_identity.g.dart b/lib/src/model/json/signal_identity.model.g.dart similarity index 95% rename from lib/src/model/json/signal_identity.g.dart rename to lib/src/model/json/signal_identity.model.g.dart index 61a2742e..e80049e6 100644 --- a/lib/src/model/json/signal_identity.g.dart +++ b/lib/src/model/json/signal_identity.model.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'signal_identity.dart'; +part of 'signal_identity.model.dart'; // ************************************************************************** // JsonSerializableGenerator diff --git a/lib/src/model/json/userdata.dart b/lib/src/model/json/userdata.model.dart similarity index 99% rename from lib/src/model/json/userdata.dart rename to lib/src/model/json/userdata.model.dart index e0e0585d..3c249e9c 100644 --- a/lib/src/model/json/userdata.dart +++ b/lib/src/model/json/userdata.model.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; -part 'userdata.g.dart'; +part 'userdata.model.g.dart'; @JsonSerializable() class UserData { diff --git a/lib/src/model/json/userdata.g.dart b/lib/src/model/json/userdata.model.g.dart similarity index 99% rename from lib/src/model/json/userdata.g.dart rename to lib/src/model/json/userdata.model.g.dart index 69d4b694..98e8bc7e 100644 --- a/lib/src/model/json/userdata.g.dart +++ b/lib/src/model/json/userdata.model.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'userdata.dart'; +part of 'userdata.model.dart'; // ************************************************************************** // JsonSerializableGenerator diff --git a/lib/src/model/purchases/purchasable_product.dart b/lib/src/model/purchasable_product.model.dart similarity index 100% rename from lib/src/model/purchases/purchasable_product.dart rename to lib/src/model/purchasable_product.model.dart diff --git a/lib/src/providers/connection.provider.dart b/lib/src/providers/connection.provider.dart index d81b3d07..29abf553 100644 --- a/lib/src/providers/connection.provider.dart +++ b/lib/src/providers/connection.provider.dart @@ -1,16 +1,18 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { CustomChangeProvider() { _connSub = apiService.onConnectionStateUpdated.listen( updateConnectionState, ); + // The API is connected before the subscription has started so ensure that the connection state is correct + _isConnected = apiService.isConnected; } - bool _isConnected = false; - bool get isConnected => _isConnected; + late bool _isConnected; late StreamSubscription _connSub; + bool get isConnected => _isConnected; @override void dispose() { diff --git a/lib/src/providers/purchases.provider.dart b/lib/src/providers/purchases.provider.dart index fb2f7c86..96eaa777 100644 --- a/lib/src/providers/purchases.provider.dart +++ b/lib/src/providers/purchases.provider.dart @@ -1,15 +1,16 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; + import 'package:flutter/foundation.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/subscription.keys.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; -import 'package:twonly/src/model/purchases/purchasable_product.dart'; +import 'package:twonly/src/model/purchasable_product.model.dart'; import 'package:twonly/src/services/subscription.service.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/storage.dart'; import 'package:url_launcher/url_launcher.dart'; // Gives the option to override in tests. diff --git a/lib/src/providers/routing.provider.dart b/lib/src/providers/routing.provider.dart index cf43cedf..d7138010 100644 --- a/lib/src/providers/routing.provider.dart +++ b/lib/src/providers/routing.provider.dart @@ -2,50 +2,50 @@ import 'package:go_router/go_router.dart'; import 'package:twonly/app.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/views/camera/camera_qr_scanner.view.dart'; -import 'package:twonly/src/views/camera/camera_send_to.view.dart'; -import 'package:twonly/src/views/chats/add_new_user.view.dart'; -import 'package:twonly/src/views/chats/archived_chats.view.dart'; -import 'package:twonly/src/views/chats/chat_messages.view.dart'; -import 'package:twonly/src/views/chats/media_viewer.view.dart'; -import 'package:twonly/src/views/chats/start_new_chat.view.dart'; -import 'package:twonly/src/views/contact/contact.view.dart'; -import 'package:twonly/src/views/groups/group.view.dart'; -import 'package:twonly/src/views/groups/group_create_select_members.view.dart'; -import 'package:twonly/src/views/onboarding/recover.view.dart'; -import 'package:twonly/src/views/public_profile.view.dart'; -import 'package:twonly/src/views/settings/account.view.dart'; -import 'package:twonly/src/views/settings/appearance.view.dart'; -import 'package:twonly/src/views/settings/backup/backup.view.dart'; -import 'package:twonly/src/views/settings/backup/backup_server.view.dart'; -import 'package:twonly/src/views/settings/backup/setup_backup.view.dart'; -import 'package:twonly/src/views/settings/chat/chat_reactions.view.dart'; -import 'package:twonly/src/views/settings/chat/chat_settings.view.dart'; -import 'package:twonly/src/views/settings/data_and_storage.view.dart'; -import 'package:twonly/src/views/settings/data_and_storage/export_media.view.dart'; -import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart'; -import 'package:twonly/src/views/settings/developer/automated_testing.view.dart'; -import 'package:twonly/src/views/settings/developer/developer.view.dart'; -import 'package:twonly/src/views/settings/developer/reduce_flames.view.dart'; -import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart'; -import 'package:twonly/src/views/settings/help/changelog.view.dart'; -import 'package:twonly/src/views/settings/help/contact_us.view.dart'; -import 'package:twonly/src/views/settings/help/credits.view.dart'; -import 'package:twonly/src/views/settings/help/diagnostics.view.dart'; -import 'package:twonly/src/views/settings/help/faq.view.dart'; -import 'package:twonly/src/views/settings/help/faq/verifybadge.dart'; -import 'package:twonly/src/views/settings/help/help.view.dart'; -import 'package:twonly/src/views/settings/notification.view.dart'; -import 'package:twonly/src/views/settings/privacy.view.dart'; -import 'package:twonly/src/views/settings/privacy/block_users.view.dart'; -import 'package:twonly/src/views/settings/privacy/user_discovery.view.dart'; -import 'package:twonly/src/views/settings/profile/modify_avatar.view.dart'; -import 'package:twonly/src/views/settings/profile/profile.view.dart'; -import 'package:twonly/src/views/settings/settings_main.view.dart'; -import 'package:twonly/src/views/settings/share_with_friends.view.dart'; -import 'package:twonly/src/views/settings/subscription/subscription.view.dart'; -import 'package:twonly/src/views/user_study/user_study_questionnaire.view.dart'; -import 'package:twonly/src/views/user_study/user_study_welcome.view.dart'; +import 'package:twonly/src/visual/views/camera/camera_qr_scanner.view.dart'; +import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart'; +import 'package:twonly/src/visual/views/chats/add_new_user.view.dart'; +import 'package:twonly/src/visual/views/chats/archived_chats.view.dart'; +import 'package:twonly/src/visual/views/chats/chat_messages.view.dart'; +import 'package:twonly/src/visual/views/chats/media_viewer.view.dart'; +import 'package:twonly/src/visual/views/chats/start_new_chat.view.dart'; +import 'package:twonly/src/visual/views/contact/contact.view.dart'; +import 'package:twonly/src/visual/views/groups/group.view.dart'; +import 'package:twonly/src/visual/views/groups/group_create_select_members.view.dart'; +import 'package:twonly/src/visual/views/onboarding/recover.view.dart'; +import 'package:twonly/src/visual/views/public_profile.view.dart'; +import 'package:twonly/src/visual/views/settings/account.view.dart'; +import 'package:twonly/src/visual/views/settings/appearance.view.dart'; +import 'package:twonly/src/visual/views/settings/backup/backup_server.view.dart'; +import 'package:twonly/src/visual/views/settings/backup/backup_settings.view.dart'; +import 'package:twonly/src/visual/views/settings/backup/backup_setup.view.dart'; +import 'package:twonly/src/visual/views/settings/chat/chat_reactions.view.dart'; +import 'package:twonly/src/visual/views/settings/chat/chat_settings.view.dart'; +import 'package:twonly/src/visual/views/settings/data_and_storage.view.dart'; +import 'package:twonly/src/visual/views/settings/data_and_storage/export_media.view.dart'; +import 'package:twonly/src/visual/views/settings/data_and_storage/import_media.view.dart'; +import 'package:twonly/src/visual/views/settings/developer/automated_testing.view.dart'; +import 'package:twonly/src/visual/views/settings/developer/developer.view.dart'; +import 'package:twonly/src/visual/views/settings/developer/reduce_flames.view.dart'; +import 'package:twonly/src/visual/views/settings/developer/retransmission_data.view.dart'; +import 'package:twonly/src/visual/views/settings/help/changelog.view.dart'; +import 'package:twonly/src/visual/views/settings/help/contact_us.view.dart'; +import 'package:twonly/src/visual/views/settings/help/credits.view.dart'; +import 'package:twonly/src/visual/views/settings/help/diagnostics.view.dart'; +import 'package:twonly/src/visual/views/settings/help/faq.view.dart'; +import 'package:twonly/src/visual/views/settings/help/faq/verification_bade_faq.view.dart'; +import 'package:twonly/src/visual/views/settings/help/help.view.dart'; +import 'package:twonly/src/visual/views/settings/notification.view.dart'; +import 'package:twonly/src/visual/views/settings/privacy.view.dart'; +import 'package:twonly/src/visual/views/settings/privacy/block_users.view.dart'; +import 'package:twonly/src/visual/views/settings/privacy/user_discovery.view.dart'; +import 'package:twonly/src/visual/views/settings/profile/modify_avatar.view.dart'; +import 'package:twonly/src/visual/views/settings/profile/profile.view.dart'; +import 'package:twonly/src/visual/views/settings/settings_main.view.dart'; +import 'package:twonly/src/visual/views/settings/share_with_friends.view.dart'; +import 'package:twonly/src/visual/views/settings/subscription/subscription.view.dart'; +import 'package:twonly/src/visual/views/user_study/user_study_questionnaire.view.dart'; +import 'package:twonly/src/visual/views/user_study/user_study_welcome.view.dart'; final routerProvider = GoRouter( routes: [ @@ -175,9 +175,7 @@ final routerProvider = GoRouter( ), GoRoute( path: 'setup', - builder: (context, state) => SetupBackupView( - isPasswordChangeOnly: state.extra as bool? ?? false, - ), + builder: (context, state) => const SetupBackupView(), ), ], ), diff --git a/lib/src/providers/settings.provider.dart b/lib/src/providers/settings.provider.dart index d1b916dd..fb859bb6 100644 --- a/lib/src/providers/settings.provider.dart +++ b/lib/src/providers/settings.provider.dart @@ -1,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:twonly/src/utils/storage.dart'; +import 'package:twonly/src/services/user.service.dart'; class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { late ThemeMode _themeMode; diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index d3c245d9..ad0d20a7 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -18,18 +18,19 @@ import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart'; import 'package:mutex/mutex.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart' as server; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart'; -import 'package:twonly/src/services/api/mediafiles/download.service.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; -import 'package:twonly/src/services/api/messages.dart'; -import 'package:twonly/src/services/api/server_messages.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/mediafiles/download.api.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; +import 'package:twonly/src/services/api/server_messages.api.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/group.services.dart'; import 'package:twonly/src/services/notifications/fcm.notifications.dart'; @@ -37,12 +38,12 @@ import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/services/subscription.service.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/services/user_discovery.service.dart'; +import 'package:twonly/src/services/user_study.service.dart'; import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/user_study/user_study_data_collection.dart'; import 'package:web_socket_channel/io.dart'; final lockConnecting = Mutex(); @@ -126,7 +127,7 @@ class ApiService { unawaited(UserDiscoveryService.checkForNewAnnouncedUsers()); - if (AppSession.currentUser.userStudyParticipantsToken != null) { + if (appSession.currentUser.userStudyParticipantsToken != null) { // In case the user participates in the user study, call the handler after authenticated, to be sure there is a internet connection unawaited(handleUserStudyUpload()); } @@ -211,7 +212,7 @@ class ApiService { }); } - bool get isConnected => _channel != null && _channel!.closeCode != null; + bool get isConnected => _channel != null && _channel!.closeCode == null; Future _onDone() async { _reconnectionDelay = 3; diff --git a/lib/src/services/api/client2client/additional_data.c2c.dart b/lib/src/services/api/client2client/additional_data.c2c.dart index 6f420932..02ffb246 100644 --- a/lib/src/services/api/client2client/additional_data.c2c.dart +++ b/lib/src/services/api/client2client/additional_data.c2c.dart @@ -1,9 +1,9 @@ import 'package:clock/clock.dart' show clock; import 'package:drift/drift.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/utils/log.dart'; Future handleAdditionalDataMessage( diff --git a/lib/src/services/api/client2client/contact.c2c.dart b/lib/src/services/api/client2client/contact.c2c.dart index 7278bf06..6817687f 100644 --- a/lib/src/services/api/client2client/contact.c2c.dart +++ b/lib/src/services/api/client2client/contact.c2c.dart @@ -2,12 +2,12 @@ import 'dart:async'; import 'dart:convert'; import 'package:drift/drift.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart' hide Message; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/utils/avatars.dart'; import 'package:twonly/src/utils/log.dart'; diff --git a/lib/src/services/api/client2client/errors.c2c.dart b/lib/src/services/api/client2client/errors.c2c.dart index 84dc8c8e..5b8093f1 100644 --- a/lib/src/services/api/client2client/errors.c2c.dart +++ b/lib/src/services/api/client2client/errors.c2c.dart @@ -1,6 +1,6 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart' show Value; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'; import 'package:twonly/src/utils/log.dart'; diff --git a/lib/src/services/api/client2client/groups.c2c.dart b/lib/src/services/api/client2client/groups.c2c.dart index 1193b4cf..073336c4 100644 --- a/lib/src/services/api/client2client/groups.c2c.dart +++ b/lib/src/services/api/client2client/groups.c2c.dart @@ -1,12 +1,13 @@ import 'dart:async'; + import 'package:drift/drift.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/group.services.dart'; import 'package:twonly/src/utils/log.dart'; @@ -161,7 +162,7 @@ Future handleGroupUpdate( case GroupActionType.demoteToMember: int? affectedContactId = update.affectedContactId.toInt(); - if (affectedContactId == AppSession.currentUser.userId) { + if (affectedContactId == appSession.currentUser.userId) { affectedContactId = null; if (actionType == GroupActionType.removedMember) { // Oh no, I just got removed from the group... diff --git a/lib/src/services/api/client2client/media.c2c.dart b/lib/src/services/api/client2client/media.c2c.dart index 496ac0bc..26375e29 100644 --- a/lib/src/services/api/client2client/media.c2c.dart +++ b/lib/src/services/api/client2client/media.c2c.dart @@ -1,14 +1,15 @@ import 'dart:async'; + import 'package:drift/drift.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart' hide Message; -import 'package:twonly/src/services/api/mediafiles/download.service.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/mediafiles/download.api.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; diff --git a/lib/src/services/api/client2client/messages.c2c.dart b/lib/src/services/api/client2client/messages.c2c.dart index d2f810f9..461846e0 100644 --- a/lib/src/services/api/client2client/messages.c2c.dart +++ b/lib/src/services/api/client2client/messages.c2c.dart @@ -1,6 +1,6 @@ -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/utils/log.dart'; Future handleMessageUpdate( diff --git a/lib/src/services/api/client2client/reaction.c2c.dart b/lib/src/services/api/client2client/reaction.c2c.dart index 9d9b5e96..77fcdcc3 100644 --- a/lib/src/services/api/client2client/reaction.c2c.dart +++ b/lib/src/services/api/client2client/reaction.c2c.dart @@ -1,7 +1,7 @@ import 'package:clock/clock.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/utils/log.dart'; Future handleReaction( diff --git a/lib/src/services/api/client2client/text_message.c2c.dart b/lib/src/services/api/client2client/text_message.c2c.dart index a189f39c..31d225b4 100644 --- a/lib/src/services/api/client2client/text_message.c2c.dart +++ b/lib/src/services/api/client2client/text_message.c2c.dart @@ -1,10 +1,10 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/utils/log.dart'; Future handleTextMessage( diff --git a/lib/src/services/api/client2client/user_discovery.c2c.dart b/lib/src/services/api/client2client/user_discovery.c2c.dart index d265b825..3ed36027 100644 --- a/lib/src/services/api/client2client/user_discovery.c2c.dart +++ b/lib/src/services/api/client2client/user_discovery.c2c.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/user_discovery.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -34,17 +34,18 @@ Future handleUserDiscoveryRequest( ) async { Log.info('Got a user discovery request'); - if (!AppSession.currentUser.isUserDiscoveryEnabled) { + if (!appSession.currentUser.isUserDiscoveryEnabled) { Log.warn('Got a user discovery request while it is disabled'); return; } final contact = await twonlyDB.contactsDao.getContactById(fromUserId); if (contact == null) return; - if (contact.mediaSendCounter < AppSession.currentUser.minimumRequiredImagesExchanged || + if (contact.mediaSendCounter < + appSession.currentUser.minimumRequiredImagesExchanged || contact.userDiscoveryExcluded) { Log.warn( - 'Got a request to update user discovery, but mediaSendCounter (${contact.mediaSendCounter}) < ${AppSession.currentUser.minimumRequiredImagesExchanged} or user is excluded ${contact.userDiscoveryExcluded}', + 'Got a request to update user discovery, but mediaSendCounter (${contact.mediaSendCounter}) < ${appSession.currentUser.minimumRequiredImagesExchanged} or user is excluded ${contact.userDiscoveryExcluded}', ); return; } @@ -72,7 +73,7 @@ Future handleUserDiscoveryUpdate( int fromUserId, EncryptedContent_UserDiscoveryUpdate update, ) async { - if (!AppSession.currentUser.isUserDiscoveryEnabled) { + if (!appSession.currentUser.isUserDiscoveryEnabled) { Log.warn('Got a user discovery update while it is disabled'); return; } diff --git a/lib/src/services/api/mediafiles/download.service.dart b/lib/src/services/api/mediafiles/download.api.dart similarity index 98% rename from lib/src/services/api/mediafiles/download.service.dart rename to lib/src/services/api/mediafiles/download.api.dart index 31e57eeb..537816fa 100644 --- a/lib/src/services/api/mediafiles/download.service.dart +++ b/lib/src/services/api/mediafiles/download.api.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; + import 'package:background_downloader/background_downloader.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; @@ -8,11 +9,11 @@ import 'package:drift/drift.dart'; import 'package:http/http.dart' as http; import 'package:mutex/mutex.dart'; import 'package:path/path.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; @@ -96,7 +97,8 @@ Future isAllowedToDownload(MediaType type) async { } final connectivityResult = await Connectivity().checkConnectivity(); - final options = AppSession.currentUser.autoDownloadOptions ?? defaultAutoDownloadOptions; + final options = + appSession.currentUser.autoDownloadOptions ?? defaultAutoDownloadOptions; if (connectivityResult.contains(ConnectivityResult.mobile)) { if (type == MediaType.video) { diff --git a/lib/src/services/api/mediafiles/media_background.service.dart b/lib/src/services/api/mediafiles/media_background.api.dart similarity index 95% rename from lib/src/services/api/mediafiles/media_background.service.dart rename to lib/src/services/api/mediafiles/media_background.api.dart index 5b1edab2..7de58a96 100644 --- a/lib/src/services/api/mediafiles/media_background.service.dart +++ b/lib/src/services/api/mediafiles/media_background.api.dart @@ -3,11 +3,11 @@ import 'dart:async'; import 'package:background_downloader/background_downloader.dart'; import 'package:drift/drift.dart' show Value; import 'package:flutter/foundation.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/api/mediafiles/download.service.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/download.api.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/backup/create.backup.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; diff --git a/lib/src/services/api/mediafiles/upload.service.dart b/lib/src/services/api/mediafiles/upload.api.dart similarity index 98% rename from lib/src/services/api/mediafiles/upload.service.dart rename to lib/src/services/api/mediafiles/upload.api.dart index 4629d26a..9a4c5e28 100644 --- a/lib/src/services/api/mediafiles/upload.service.dart +++ b/lib/src/services/api/mediafiles/upload.api.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; + import 'package:background_downloader/background_downloader.dart'; import 'package:clock/clock.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; @@ -11,15 +12,16 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; import 'package:mutex/mutex.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/mediafiles/media_background.service.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/mediafiles/media_background.api.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/log.dart'; @@ -357,7 +359,7 @@ Future startBackgroundMediaUpload(MediaFileService mediaService) async { // if the user has enabled auto storing and the file // was send with unlimited counter not in twonly-Mode then store the file - if (AppSession.currentUser.autoStoreAllSendUnlimitedMediaFiles && + if (appSession.currentUser.autoStoreAllSendUnlimitedMediaFiles && !mediaService.mediaFile.requiresAuthentication && !mediaService.storedPath.existsSync() && mediaService.mediaFile.displayLimitInMilliseconds == null) { @@ -595,7 +597,7 @@ Future _uploadUploadRequest(MediaFileService media) async { if (AppState.isInBackgroundTask || !connectivityResult.contains(ConnectivityResult.mobile) && - !connectivityResult.contains(ConnectivityResult.wifi)) { + !connectivityResult.contains(ConnectivityResult.wifi)) { // no internet, directly put it into the background... await FileDownloader().enqueue(task); await media.setUploadState(UploadState.backgroundUploadTaskStarted); diff --git a/lib/src/services/api/messages.dart b/lib/src/services/api/messages.api.dart similarity index 96% rename from lib/src/services/api/messages.dart rename to lib/src/services/api/messages.api.dart index cad90fc5..17a530f7 100644 --- a/lib/src/services/api/messages.dart +++ b/lib/src/services/api/messages.api.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; + import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:fixnum/fixnum.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:mutex/mutex.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; @@ -345,12 +346,12 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( return null; } } - encryptedContent.senderProfileCounter = Int64(AppSession.currentUser.avatarCounter); + encryptedContent.senderProfileCounter = Int64(appSession.currentUser.avatarCounter); - if (AppSession.currentUser.isUserDiscoveryEnabled && messageId != null) { + if (appSession.currentUser.isUserDiscoveryEnabled && messageId != null) { final contact = await twonlyDB.contactsDao.getContactById(contactId); if (contact != null && - contact.mediaSendCounter >= AppSession.currentUser.minimumRequiredImagesExchanged && + contact.mediaSendCounter >= appSession.currentUser.minimumRequiredImagesExchanged && !contact.userDiscoveryExcluded) { final version = await UserDiscoveryService.getCurrentVersion(); if (version != null) { @@ -406,7 +407,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText( } Future sendTypingIndication(String groupId, bool isTyping) async { - if (!AppSession.currentUser.typingIndicators) return; + if (!appSession.currentUser.typingIndicators) return; await sendCipherTextToGroup( groupId, pb.EncryptedContent( @@ -462,15 +463,15 @@ Future notifyContactAboutOpeningMessage( Future sendContactMyProfileData(int contactId) async { List? avatarSvgCompressed; - if (AppSession.currentUser.avatarSvg != null) { - avatarSvgCompressed = gzip.encode(utf8.encode(AppSession.currentUser.avatarSvg!)); + if (appSession.currentUser.avatarSvg != null) { + avatarSvgCompressed = gzip.encode(utf8.encode(appSession.currentUser.avatarSvg!)); } final encryptedContent = pb.EncryptedContent( contactUpdate: pb.EncryptedContent_ContactUpdate( type: pb.EncryptedContent_ContactUpdate_Type.UPDATE, avatarSvgCompressed: avatarSvgCompressed, - displayName: AppSession.currentUser.displayName, - username: AppSession.currentUser.username, + displayName: appSession.currentUser.displayName, + username: appSession.currentUser.username, ), ); await sendCipherText(contactId, encryptedContent); diff --git a/lib/src/services/api/server_messages.dart b/lib/src/services/api/server_messages.api.dart similarity index 97% rename from lib/src/services/api/server_messages.dart rename to lib/src/services/api/server_messages.api.dart index 4b176870..fbf3bf7f 100644 --- a/lib/src/services/api/server_messages.dart +++ b/lib/src/services/api/server_messages.api.dart @@ -1,10 +1,12 @@ import 'dart:async'; import 'dart:io'; + import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:hashlib/random.dart'; import 'package:mutex/mutex.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart' hide Message; import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart' @@ -25,7 +27,7 @@ import 'package:twonly/src/services/api/client2client/pushkeys.c2c.dart'; import 'package:twonly/src/services/api/client2client/reaction.c2c.dart'; import 'package:twonly/src/services/api/client2client/text_message.c2c.dart'; import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/group.services.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; import 'package:twonly/src/services/signal/encryption.signal.dart'; @@ -263,7 +265,8 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( await twonlyDB.receiptsDao.markMessagesForRetry(fromUserId); final senderProfileCounter = await checkForProfileUpdate(fromUserId, content); - if (AppSession.currentUser.isUserDiscoveryEnabled && content.hasSenderUserDiscoveryVersion()) { + if (appSession.currentUser.isUserDiscoveryEnabled && + content.hasSenderUserDiscoveryVersion()) { await checkForUserDiscoveryChanges( fromUserId, content.senderUserDiscoveryVersion, @@ -351,7 +354,8 @@ Future<(EncryptedContent?, PlaintextContent?)> handleEncryptedMessage( /// Verify that the user is (still) in that group... if (!await twonlyDB.groupsDao.isContactInGroup(fromUserId, content.groupId)) { - if (getUUIDforDirectChat(AppSession.currentUser.userId, fromUserId) == content.groupId) { + if (getUUIDforDirectChat(appSession.currentUser.userId, fromUserId) == + content.groupId) { final contact = await twonlyDB.contactsDao .getContactByUserId(fromUserId) .getSingleOrNull(); diff --git a/lib/src/services/api/utils.dart b/lib/src/services/api/utils.api.dart similarity index 96% rename from lib/src/services/api/utils.dart rename to lib/src/services/api/utils.api.dart index 81b084b1..13ca8f05 100644 --- a/lib/src/services/api/utils.dart +++ b/lib/src/services/api/utils.api.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; import 'package:fixnum/fixnum.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart' @@ -11,7 +11,7 @@ import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart as server; import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart' hide Message; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; diff --git a/lib/src/services/background/callback_dispatcher.background.dart b/lib/src/services/background/callback_dispatcher.background.dart index e154b5ae..65e1b8fc 100644 --- a/lib/src/services/background/callback_dispatcher.background.dart +++ b/lib/src/services/background/callback_dispatcher.background.dart @@ -1,15 +1,15 @@ import 'dart:async'; + import 'package:mutex/mutex.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/keyvalue.keys.dart'; -import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/api.service.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/exclusive_access.dart'; import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/storage.dart'; import 'package:workmanager/workmanager.dart'; // ignore: unreachable_from_main @@ -56,7 +56,7 @@ Future initBackgroundExecution() async { // stay alive for multiple hours between task executions final user = await getUser(); if (user == null) return false; - AppSession.currentUser = user; + appSession.currentUser = user; return true; } @@ -66,10 +66,10 @@ Future initBackgroundExecution() async { final user = await getUser(); if (user == null) return false; - AppSession.currentUser = user; - twonlyDB = TwonlyDB(); - apiService = ApiService(); + setupLocator(); + appSession.currentUser = user; + AppState.isInBackgroundTask = true; _isInitialized = true; diff --git a/lib/src/services/backup/common.backup.dart b/lib/src/services/backup/common.backup.dart index 4fc9080b..692a8220 100644 --- a/lib/src/services/backup/common.backup.dart +++ b/lib/src/services/backup/common.backup.dart @@ -1,19 +1,20 @@ import 'dart:async'; import 'dart:convert'; + import 'package:drift/drift.dart'; import 'package:hashlib/hashlib.dart'; import 'package:http/http.dart' as http; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/model/json/userdata.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/model/json/userdata.model.dart'; import 'package:twonly/src/services/backup/create.backup.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; Future enableTwonlySafe(String password) async { final (backupId, encryptionKey) = await getMasterKey( password, - AppSession.currentUser.username, + appSession.currentUser.username, ); await updateUser((user) { @@ -65,10 +66,10 @@ Future<(Uint8List, Uint8List)> getMasterKey( } String? getTwonlySafeBackupUrl() { - if (AppSession.currentUser.twonlySafeBackup == null) return null; + if (appSession.currentUser.twonlySafeBackup == null) return null; return getTwonlySafeBackupUrlFromServer( - AppSession.currentUser.twonlySafeBackup!.backupId, - AppSession.currentUser.backupServer, + appSession.currentUser.twonlySafeBackup!.backupId, + appSession.currentUser.backupServer, ); } diff --git a/lib/src/services/backup/create.backup.dart b/lib/src/services/backup/create.backup.dart index 6a5e6421..6038021b 100644 --- a/lib/src/services/backup/create.backup.dart +++ b/lib/src/services/backup/create.backup.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; + import 'package:background_downloader/background_downloader.dart'; import 'package:clock/clock.dart'; import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; @@ -11,28 +12,29 @@ import 'package:drift_flutter/drift_flutter.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:path/path.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/model/json/userdata.dart'; +import 'package:twonly/src/model/json/userdata.model.dart'; import 'package:twonly/src/model/protobuf/client/generated/backup.pb.dart'; import 'package:twonly/src/services/backup/common.backup.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; Future performTwonlySafeBackup({bool force = false}) async { - if (AppSession.currentUser.twonlySafeBackup == null) { + if (appSession.currentUser.twonlySafeBackup == null) { return; } - if (AppSession.currentUser.twonlySafeBackup!.backupUploadState == + if (appSession.currentUser.twonlySafeBackup!.backupUploadState == LastBackupUploadState.pending) { Log.warn('Backup upload is already pending.'); return; } final lastUpdateTime = - AppSession.currentUser.twonlySafeBackup!.lastBackupDone; + appSession.currentUser.twonlySafeBackup!.lastBackupDone; if (!force && lastUpdateTime != null) { if (lastUpdateTime.isAfter(clock.now().subtract(const Duration(days: 1)))) { return; @@ -120,8 +122,8 @@ Future performTwonlySafeBackup({bool force = false}) async { final backupHash = uint8ListToHex((await Sha256().hash(backupBytes)).bytes); - if (AppSession.currentUser.twonlySafeBackup!.lastBackupDone == null || - AppSession.currentUser.twonlySafeBackup!.lastBackupDone!.isAfter( + if (appSession.currentUser.twonlySafeBackup!.lastBackupDone == null || + appSession.currentUser.twonlySafeBackup!.lastBackupDone!.isAfter( clock.now().subtract(const Duration(days: 90)), )) { force = true; @@ -150,7 +152,7 @@ Future performTwonlySafeBackup({bool force = false}) async { final secretBox = await chacha20.encrypt( backupBytes, secretKey: SecretKey( - AppSession.currentUser.twonlySafeBackup!.encryptionKey, + appSession.currentUser.twonlySafeBackup!.encryptionKey, ), nonce: nonce, ); @@ -173,9 +175,9 @@ Future performTwonlySafeBackup({bool force = false}) async { 'Create twonly Backup with a size of ${encryptedBackupBytes.length} bytes.', ); - if (AppSession.currentUser.backupServer != null) { + if (appSession.currentUser.backupServer != null) { if (encryptedBackupBytes.length > - AppSession.currentUser.backupServer!.maxBackupBytes) { + appSession.currentUser.backupServer!.maxBackupBytes) { Log.error('Backup is to big for the alternative backup server.'); await updateUser((user) { user.twonlySafeBackup!.backupUploadState = LastBackupUploadState.failed; diff --git a/lib/src/services/backup/restore.backup.dart b/lib/src/services/backup/restore.backup.dart index 2ec13760..7d0fbe7c 100644 --- a/lib/src/services/backup/restore.backup.dart +++ b/lib/src/services/backup/restore.backup.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; + import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:drift/drift.dart'; @@ -9,12 +10,12 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; -import 'package:twonly/src/model/json/userdata.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; +import 'package:twonly/src/model/json/userdata.model.dart'; import 'package:twonly/src/model/protobuf/client/generated/backup.pb.dart'; import 'package:twonly/src/services/backup/common.backup.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/storage.dart'; Future recoverBackup( String username, @@ -23,10 +24,7 @@ Future recoverBackup( ) async { final (backupId, encryptionKey) = await getMasterKey(password, username); - final backupServerUrl = await getTwonlySafeBackupUrlFromServer( - backupId, - server, - ); + final backupServerUrl = getTwonlySafeBackupUrlFromServer(backupId, server); if (backupServerUrl == null) { Log.error('Could not create backup url'); diff --git a/lib/src/services/flame.service.dart b/lib/src/services/flame.service.dart index dcba381b..353c6a16 100644 --- a/lib/src/services/flame.service.dart +++ b/lib/src/services/flame.service.dart @@ -2,12 +2,12 @@ import 'package:clock/clock.dart'; import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:fixnum/fixnum.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; Future syncFlameCounters({String? forceForGroup}) async { final groups = await twonlyDB.groupsDao.getAllGroups(); @@ -17,7 +17,7 @@ Future syncFlameCounters({String? forceForGroup}) async { (x) => x.totalMediaCounter == maxMessageCounter, ); - if (AppSession.currentUser.myBestFriendGroupId != bestFriend.groupId) { + if (appSession.currentUser.myBestFriendGroupId != bestFriend.groupId) { await updateUser((user) { user.myBestFriendGroupId = bestFriend.groupId; }); diff --git a/lib/src/services/group.services.dart b/lib/src/services/group.services.dart index 4ed4d399..a474dbd1 100644 --- a/lib/src/services/group.services.dart +++ b/lib/src/services/group.services.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; + import 'package:clock/clock.dart'; import 'package:collection/collection.dart'; import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; @@ -13,13 +14,13 @@ import 'package:http/http.dart' as http; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; // ignore: implementation_imports import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/groups.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/notifications/pushkeys.notifications.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/log.dart'; @@ -42,8 +43,8 @@ Future createNewGroup(String groupName, List members) async { final memberIds = members.map((x) => Int64(x.userId)).toList(); final groupState = EncryptedGroupState( - memberIds: [Int64(AppSession.currentUser.userId)] + memberIds, - adminIds: [Int64(AppSession.currentUser.userId)], + memberIds: [Int64(appSession.currentUser.userId)] + memberIds, + adminIds: [Int64(appSession.currentUser.userId)], groupName: groupName, deleteMessagesAfterMilliseconds: Int64( defaultDeleteMessagesAfterMilliseconds, @@ -283,9 +284,9 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { final myPubKey = keyPair.getPublicKey().serialize().toList(); if (listEquals(appendedPubKey, myPubKey)) { - adminIds.remove(Int64(AppSession.currentUser.userId)); + adminIds.remove(Int64(appSession.currentUser.userId)); memberIds.remove( - Int64(AppSession.currentUser.userId), + Int64(appSession.currentUser.userId), ); // -> Will remove the user later... } else { Log.info('A non admin left the group!!!'); @@ -303,7 +304,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { } } - if (!memberIds.contains(Int64(AppSession.currentUser.userId))) { + if (!memberIds.contains(Int64(appSession.currentUser.userId))) { // OH no, I am no longer a member of this group... // Return from the group... await twonlyDB.groupsDao.updateGroup( @@ -316,7 +317,10 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { } final isGroupAdmin = - adminIds.firstWhereOrNull((t) => t.toInt() == AppSession.currentUser.userId) != null; + adminIds.firstWhereOrNull( + (t) => t.toInt() == appSession.currentUser.userId, + ) != + null; if (!listEquals(memberIds, encryptedGroupState.memberIds)) { if (isGroupAdmin) { @@ -368,7 +372,7 @@ Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async { // First find and insert NEW members for (final memberId in memberIds) { - if (memberId == Int64(AppSession.currentUser.userId)) { + if (memberId == Int64(appSession.currentUser.userId)) { continue; } if (currentGroupMembers.any((t) => t.contactId == memberId.toInt())) { @@ -838,7 +842,9 @@ Future removeMemberFromGroup( groupId: Value(group.groupId), type: const Value(GroupActionType.removedMember), affectedContactId: Value( - removeContactId == AppSession.currentUser.userId ? null : removeContactId, + removeContactId == appSession.currentUser.userId + ? null + : removeContactId, ), ), ); @@ -945,7 +951,7 @@ Future leaveAsNonAdminFromGroup(Group group) async { EncryptedContent( groupUpdate: EncryptedContent_GroupUpdate( groupActionType: groupActionType.name, - affectedContactId: Int64(AppSession.currentUser.userId), + affectedContactId: Int64(appSession.currentUser.userId), ), ), ); diff --git a/lib/src/services/intent/links.intent.dart b/lib/src/services/intent/links.intent.dart index a71504eb..ebc6e8ea 100644 --- a/lib/src/services/intent/links.intent.dart +++ b/lib/src/services/intent/links.intent.dart @@ -2,23 +2,24 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; + import 'package:collection/collection.dart'; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:go_router/go_router.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_editor.view.dart'; -import 'package:twonly/src/views/chats/add_new_user.view.dart'; -import 'package:twonly/src/views/components/alert_dialog.dart'; +import 'package:twonly/src/visual/components/alert.dialog.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart'; +import 'package:twonly/src/visual/views/chats/add_new_user.view.dart'; Future handleIntentUrl(BuildContext context, Uri uri) async { if (!uri.scheme.startsWith('http')) return false; @@ -32,7 +33,7 @@ Future handleIntentUrl(BuildContext context, Uri uri) async { if (!context.mounted) return false; - if (username == AppSession.currentUser.username) { + if (username == appSession.currentUser.username) { await context.push(Routes.settingsPublicProfile); return true; } @@ -115,7 +116,7 @@ Future handleIntentMediaFile( final newMediaService = await initializeMediaUpload( type, - AppSession.currentUser.defaultShowTime, + appSession.currentUser.defaultShowTime, ); if (newMediaService == null) { Log.error('Could not create new media file for intent shared file'); diff --git a/lib/src/services/mediafiles/compression.service.dart b/lib/src/services/mediafiles/compression.service.dart index f79a4e1a..d0c8afb4 100644 --- a/lib/src/services/mediafiles/compression.service.dart +++ b/lib/src/services/mediafiles/compression.service.dart @@ -1,10 +1,11 @@ import 'dart:async'; import 'dart:io'; + import 'package:drift/drift.dart' show Value; import 'package:flutter/foundation.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:pro_video_editor/pro_video_editor.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/channels/video_compression.channel.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; diff --git a/lib/src/services/mediafiles/mediafile.service.dart b/lib/src/services/mediafiles/mediafile.service.dart index 92d1b1f9..4c0ba7ac 100644 --- a/lib/src/services/mediafiles/mediafile.service.dart +++ b/lib/src/services/mediafiles/mediafile.service.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'dart:io'; + import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:path/path.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/mediafiles/compression.service.dart'; @@ -237,7 +239,7 @@ class MediaFileService { } if (tempPath.existsSync()) { await tempPath.copy(storedPath.path); - if (AppSession.currentUser.storeMediaFilesInGallery) { + if (appSession.currentUser.storeMediaFilesInGallery) { if (mediaFile.type == MediaType.video) { await saveVideoToGallery(storedPath.path); } else { diff --git a/lib/src/services/notifications/background.notifications.dart b/lib/src/services/notifications/background.notifications.dart index d18bdabd..45ab7a0f 100644 --- a/lib/src/services/notifications/background.notifications.dart +++ b/lib/src/services/notifications/background.notifications.dart @@ -7,7 +7,7 @@ import 'package:cryptography_plus/cryptography_plus.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:path_provider/path_provider.dart'; import 'package:twonly/src/constants/routes.keys.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/localization/generated/app_localizations_de.dart'; import 'package:twonly/src/localization/generated/app_localizations_en.dart'; diff --git a/lib/src/services/notifications/fcm.notifications.dart b/lib/src/services/notifications/fcm.notifications.dart index 0d5d9098..27056aa2 100644 --- a/lib/src/services/notifications/fcm.notifications.dart +++ b/lib/src/services/notifications/fcm.notifications.dart @@ -8,11 +8,12 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/services/background/callback_dispatcher.background.dart'; import 'package:twonly/src/services/notifications/background.notifications.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/storage.dart'; import '../../../firebase_options.dart'; @@ -73,7 +74,7 @@ Future checkForTokenUpdates() async { } Future initFCMAfterAuthenticated({bool force = false}) async { - if (AppSession.currentUser.updateFCMToken || force) { + if (appSession.currentUser.updateFCMToken || force) { const storage = FlutterSecureStorage(); final storedToken = await storage.read(key: SecureStorageKeys.googleFcm); if (storedToken != null) { diff --git a/lib/src/services/notifications/pushkeys.notifications.dart b/lib/src/services/notifications/pushkeys.notifications.dart index a9ce4e13..b5bf8b35 100644 --- a/lib/src/services/notifications/pushkeys.notifications.dart +++ b/lib/src/services/notifications/pushkeys.notifications.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; + import 'package:clock/clock.dart'; import 'package:collection/collection.dart'; import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart'; @@ -9,14 +10,14 @@ import 'package:fixnum/fixnum.dart'; import 'package:flutter/services.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hashlib/random.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/push_notification.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; diff --git a/lib/src/services/signal/encryption.signal.dart b/lib/src/services/signal/encryption.signal.dart index 7a6efcd3..e9faaaab 100644 --- a/lib/src/services/signal/encryption.signal.dart +++ b/lib/src/services/signal/encryption.signal.dart @@ -5,7 +5,7 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/src/invalid_message_exception.dart'; import 'package:mutex/mutex.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; import 'package:twonly/src/utils/log.dart'; diff --git a/lib/src/services/signal/identity.signal.dart b/lib/src/services/signal/identity.signal.dart index 3c41e054..65737607 100644 --- a/lib/src/services/signal/identity.signal.dart +++ b/lib/src/services/signal/identity.signal.dart @@ -1,15 +1,16 @@ import 'dart:convert'; + import 'package:clock/clock.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; -import 'package:twonly/src/database/signal/connect_signal_protocol_store.dart'; -import 'package:twonly/src/model/json/signal_identity.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; +import 'package:twonly/src/database/signal/signal_protocol_store.dart'; +import 'package:twonly/src/model/json/signal_identity.model.dart'; import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/storage.dart'; Future getSignalIdentityKeyPair() async { final signalIdentity = await getSignalIdentity(); @@ -20,10 +21,10 @@ Future getSignalIdentityKeyPair() async { // This function runs after the clients authenticated with the server. // It then checks if it should update a new session key Future signalHandleNewServerConnection() async { - if (AppSession.currentUser.signalLastSignedPreKeyUpdated != null) { + if (appSession.currentUser.signalLastSignedPreKeyUpdated != null) { final fortyEightHoursAgo = clock.now().subtract(const Duration(hours: 48)); final isYoungerThan48Hours = - (AppSession.currentUser.signalLastSignedPreKeyUpdated!).isAfter( + (appSession.currentUser.signalLastSignedPreKeyUpdated!).isAfter( fortyEightHoursAgo, ); if (isYoungerThan48Hours) { @@ -104,7 +105,7 @@ Future createIfNotExistsSignalIdentity() async { final identityKeyPair = generateIdentityKeyPair(); final registrationId = generateRegistrationId(true); - final signalStore = ConnectSignalProtocolStore( + final signalStore = SignalSignalProtocolStore( identityKeyPair, registrationId, ); diff --git a/lib/src/services/signal/session.signal.dart b/lib/src/services/signal/session.signal.dart index d5d07a0e..804adc00 100644 --- a/lib/src/services/signal/session.signal.dart +++ b/lib/src/services/signal/session.signal.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; + import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; diff --git a/lib/src/services/signal/utils.signal.dart b/lib/src/services/signal/utils.signal.dart index 566fd8e4..fb399884 100644 --- a/lib/src/services/signal/utils.signal.dart +++ b/lib/src/services/signal/utils.signal.dart @@ -1,21 +1,21 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.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/database/signal/signal_protocol_store.dart'; +import 'package:twonly/src/model/json/signal_identity.model.dart'; import 'package:twonly/src/services/signal/consts.signal.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; -Future getSignalStore() async { +Future getSignalStore() async { return getSignalStoreFromIdentity((await getSignalIdentity())!); } -Future getSignalStoreFromIdentity( +Future getSignalStoreFromIdentity( SignalIdentity signalIdentity, ) async { final identityKeyPair = IdentityKeyPair.fromSerialized( signalIdentity.identityKeyPairU8List, ); - return ConnectSignalProtocolStore( + return SignalSignalProtocolStore( identityKeyPair, signalIdentity.registrationId, ); diff --git a/lib/src/services/user.service.dart b/lib/src/services/user.service.dart new file mode 100644 index 00000000..d9518a40 --- /dev/null +++ b/lib/src/services/user.service.dart @@ -0,0 +1,91 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:mutex/mutex.dart'; +import 'package:provider/provider.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/constants/secure_storage.keys.dart'; +import 'package:twonly/src/model/json/userdata.model.dart'; +import 'package:twonly/src/providers/purchases.provider.dart'; +import 'package:twonly/src/services/subscription.service.dart'; +import 'package:twonly/src/utils/log.dart'; + +class UserService { + late UserData currentUser; + + final _userDataUpdateController = StreamController.broadcast(); + Stream get onUserUpdated => _userDataUpdateController.stream; + + void triggerUserUpdate() { + _userDataUpdateController.add(null); + } + + void dispose() { + _userDataUpdateController.close(); + } +} + +Future isUserCreated() async { + final user = await getUser(); + if (user == null) { + return false; + } + appSession.currentUser = user; + return true; +} + +Future getUser() async { + try { + final userJson = await const FlutterSecureStorage().read( + key: SecureStorageKeys.userData, + ); + if (userJson == null) { + return null; + } + final userMap = jsonDecode(userJson) as Map; + final user = UserData.fromJson(userMap); + return user; + } catch (e) { + Log.error('Error getting user: $e'); + return null; + } +} + +Future updateUsersPlan( + BuildContext context, + SubscriptionPlan plan, +) async { + context.read().plan = plan; + + await updateUser((user) { + user.subscriptionPlan = plan.name; + }); + + if (!context.mounted) return; + context.read().updatePlan(plan); +} + +Mutex updateProtection = Mutex(); + +Future updateUser( + void Function(UserData userData) updateUser, +) async { + await updateProtection.protect(() async { + final user = await getUser(); + if (user == null) return; + if (user.defaultShowTime == 999999) { + // This was the old version for infinity -> change it to null + user.defaultShowTime = null; + } + updateUser(user); + await const FlutterSecureStorage().write( + key: SecureStorageKeys.userData, + value: jsonEncode(user), + ); + appSession.currentUser = user; + }); + + appSession.triggerUserUpdate(); +} diff --git a/lib/src/services/user_discovery.service.dart b/lib/src/services/user_discovery.service.dart index e69a405c..1f1eea9c 100644 --- a/lib/src/services/user_discovery.service.dart +++ b/lib/src/services/user_discovery.service.dart @@ -1,14 +1,15 @@ import 'dart:convert'; + import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:flutter/foundation.dart'; import 'package:twonly/core/bridge/wrapper/user_discovery.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/user_discovery/types.pb.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/qr.dart'; -import 'package:twonly/src/utils/storage.dart'; class UserDiscoveryService { static Future checkForNewAnnouncedUsers() async { @@ -53,7 +54,7 @@ class UserDiscoveryService { try { await FlutterUserDiscovery.initializeOrUpdate( threshold: threshold, - userId: AppSession.currentUser.userId, + userId: appSession.currentUser.userId, publicKey: await getUserPublicKey(), ); await updateUser( diff --git a/lib/src/views/user_study/user_study_data_collection.dart b/lib/src/services/user_study.service.dart similarity index 88% rename from lib/src/views/user_study/user_study_data_collection.dart rename to lib/src/services/user_study.service.dart index d5020d82..30d06220 100644 --- a/lib/src/views/user_study/user_study_data_collection.dart +++ b/lib/src/services/user_study.service.dart @@ -1,11 +1,11 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/keyvalue.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; const userStudySurveyKey = 'user_study_survey'; @@ -15,7 +15,7 @@ const surveyUrlBase = 'https://survey.twonly.org/upload.php'; Future handleUserStudyUpload() async { try { - final token = AppSession.currentUser.userStudyParticipantsToken; + final token = appSession.currentUser.userStudyParticipantsToken; if (token == null) return; // in case the survey was taken offline try again @@ -35,8 +35,8 @@ Future handleUserStudyUpload() async { await KeyValueStore.delete(userStudySurveyKey); } - if (AppSession.currentUser.lastUserStudyDataUpload != null && - isToday(AppSession.currentUser.lastUserStudyDataUpload!)) { + if (appSession.currentUser.lastUserStudyDataUpload != null && + isToday(appSession.currentUser.lastUserStudyDataUpload!)) { // Only send updates once a day. // This enables to see if improvements to actually work. return; diff --git a/lib/src/themes/colors.dart b/lib/src/themes/colors.dart deleted file mode 100644 index e236bc54..00000000 --- a/lib/src/themes/colors.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'dart:ui'; - -class DefaultColors { - static const messageSelf = Color.fromARGB(255, 58, 136, 102); - static const messageOther = Color.fromARGB(233, 68, 137, 255); -} diff --git a/lib/src/utils/avatars.dart b/lib/src/utils/avatars.dart index f31b611c..ba5bdd51 100644 --- a/lib/src/utils/avatars.dart +++ b/lib/src/utils/avatars.dart @@ -1,10 +1,16 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:ui' as ui; + import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/locator.dart'; + +String getAvatarSvg(Uint8List avatarSvgCompressed) { + return utf8.decode(gzip.decode(avatarSvgCompressed)); +} Future createPushAvatars({int? forceForUserId}) async { final contacts = await twonlyDB.contactsDao.getAllContacts(); @@ -47,13 +53,13 @@ File avatarPNGFile(int contactId) { } Future getUserAvatar() async { - if (AppSession.currentUser.avatarSvg == null) { + if (appSession.currentUser.avatarSvg == null) { final data = await rootBundle.load('assets/images/default_avatar.png'); return data.buffer.asUint8List(); } final pictureInfo = await vg.loadPicture( - SvgStringLoader(AppSession.currentUser.avatarSvg!), + SvgStringLoader(appSession.currentUser.avatarSvg!), null, ); @@ -62,7 +68,8 @@ Future getUserAvatar() async { final byteData = await image.toByteData(format: ui.ImageByteFormat.png); final pngBytes = byteData!.buffer.asUint8List(); - final file = avatarPNGFile(AppSession.currentUser.userId)..writeAsBytesSync(pngBytes); + final file = avatarPNGFile(appSession.currentUser.userId) + ..writeAsBytesSync(pngBytes); pictureInfo.picture.dispose(); return file.readAsBytesSync(); diff --git a/lib/src/utils/misc.dart b/lib/src/utils/misc.dart index 2fd324ea..370ca3f4 100644 --- a/lib/src/utils/misc.dart +++ b/lib/src/utils/misc.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'package:clock/clock.dart'; @@ -11,8 +10,6 @@ import 'package:gal/gal.dart'; import 'package:intl/intl.dart'; import 'package:local_auth/local_auth.dart'; import 'package:provider/provider.dart'; -import 'package:twonly/src/database/tables/mediafiles.table.dart'; -import 'package:twonly/src/database/tables/messages.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/localization/generated/app_localizations.dart'; import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; @@ -131,30 +128,6 @@ String formatDuration(BuildContext context, int seconds) { } } -InputDecoration getInputDecoration(BuildContext context, String hintText) { - final primaryColor = Theme.of(context).colorScheme.primary; - return InputDecoration( - hintText: hintText, - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(9), - borderSide: BorderSide(color: primaryColor), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Theme.of(context).colorScheme.outline), - ), - contentPadding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), - ); -} - -Future getCompressedImage(Uint8List imageBytes) async { - final result = await FlutterImageCompress.compressWithList( - imageBytes, - quality: 90, - ); - return result; -} - Future authenticateUser( String localizedReason, { bool force = true, @@ -180,27 +153,6 @@ Future authenticateUser( return false; } -Uint8List intToBytes(int value) { - final byteData = ByteData(4)..setInt32(0, value); - return byteData.buffer.asUint8List(); -} - -int bytesToInt(Uint8List bytes) { - final byteData = ByteData.sublistView(bytes); - return byteData.getInt32(0); -} - -List? removeLastXBytes(Uint8List original, int count) { - if (original.length < count) { - return null; - } - final newList = Uint8List(original.length - count) - ..setAll(0, original.sublist(0, original.length - count)); - - final lastXBytes = original.sublist(original.length - count); - return [newList, lastXBytes]; -} - bool isDarkMode(BuildContext context) { final selectedTheme = context.read().themeMode; @@ -218,31 +170,6 @@ bool isToday(DateTime lastImageSend) { lastImageSend.day == now.day; } -InputDecoration inputTextMessageDeco(BuildContext context) { - return InputDecoration( - hintText: context.lang.chatListDetailInput, - contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2, - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: BorderSide( - color: Theme.of(context).colorScheme.primary, - width: 2, - ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(20), - borderSide: const BorderSide(color: Colors.grey, width: 2), - ), - ); -} - String truncateString(String input, {int maxLength = 20}) { if (input.length > maxLength) { return '${input.characters.take(maxLength)}...'; @@ -301,35 +228,6 @@ Uint8List hexToUint8List(String hex) => Uint8List.fromList( ), ); -Color getMessageColorFromType( - Message message, - MediaFile? mediaFile, - BuildContext context, -) { - Color color; - - if (message.type == MessageType.restoreFlameCounter.name) { - color = Colors.orange; - } else if (message.type == MessageType.text.name) { - color = Colors.blueAccent; - } else if (mediaFile != null) { - if (mediaFile.requiresAuthentication) { - color = context.color.primary; - } else { - if (mediaFile.type == MediaType.video) { - color = const Color.fromARGB(255, 243, 33, 208); - } else if (mediaFile.type == MediaType.audio) { - color = const Color.fromARGB(255, 252, 149, 85); - } else { - color = Colors.redAccent; - } - } - } else { - return (isDarkMode(context)) ? Colors.white : Colors.black; - } - return color; -} - String getUUIDforDirectChat(int a, int b) { if (a < 0 || b < 0) { throw ArgumentError('Inputs must be non-negative integers.'); @@ -385,17 +283,6 @@ String friendlyDateTime( return '$timePart $datePart'; } -String getAvatarSvg(Uint8List avatarSvgCompressed) { - final raw = gzip.decode(avatarSvgCompressed); - return utf8.decode(raw); -} - -void printWrapped(String text) { - final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk - // ignore: avoid_print - pattern.allMatches(text).forEach((match) => print(match.group(0))); -} - Future> sha256File(File file) async { final input = file.openRead(); final sha256Sink = AccumulatorSink(); diff --git a/lib/src/utils/qr.dart b/lib/src/utils/qr.dart index ad315b73..c3b4d47b 100644 --- a/lib/src/utils/qr.dart +++ b/lib/src/utils/qr.dart @@ -1,11 +1,11 @@ import 'package:drift/drift.dart' show Value; import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart'; -import 'package:twonly/src/services/api/utils.dart'; +import 'package:twonly/src/services/api/utils.api.dart'; import 'package:twonly/src/services/signal/identity.signal.dart'; import 'package:twonly/src/services/signal/utils.signal.dart'; @@ -17,8 +17,8 @@ Future getProfileQrCodeData() async { final signedPreKey = (await signalStore.loadSignedPreKeys())[0]; final publicProfile = PublicProfile( - userId: Int64(AppSession.currentUser.userId), - username: AppSession.currentUser.username, + userId: Int64(appSession.currentUser.userId), + username: appSession.currentUser.username, publicIdentityKey: (await signalStore.getIdentityKeyPair()) .getPublicKey() .serialize(), diff --git a/lib/src/utils/storage.dart b/lib/src/utils/storage.dart index f7e35e75..b963b693 100644 --- a/lib/src/utils/storage.dart +++ b/lib/src/utils/storage.dart @@ -1,78 +1,5 @@ -import 'dart:convert'; -import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:mutex/mutex.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:provider/provider.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/secure_storage_keys.dart'; -import 'package:twonly/src/model/json/userdata.dart'; -import 'package:twonly/src/providers/purchases.provider.dart'; -import 'package:twonly/src/services/subscription.service.dart'; -import 'package:twonly/src/utils/log.dart'; - -Future isUserCreated() async { - final user = await getUser(); - if (user == null) { - return false; - } - AppSession.currentUser = user; - return true; -} - -Future getUser() async { - try { - final userJson = await const FlutterSecureStorage().read( - key: SecureStorageKeys.userData, - ); - if (userJson == null) { - return null; - } - final userMap = jsonDecode(userJson) as Map; - final user = UserData.fromJson(userMap); - return user; - } catch (e) { - Log.error('Error getting user: $e'); - return null; - } -} - -Future updateUsersPlan( - BuildContext context, - SubscriptionPlan plan, -) async { - context.read().plan = plan; - - await updateUser((user) { - user.subscriptionPlan = plan.name; - }); - - if (!context.mounted) return; - context.read().updatePlan(plan); -} - -Mutex updateProtection = Mutex(); - -Future updateUser( - void Function(UserData userData) updateUser, -) async { - await updateProtection.protect(() async { - final user = await getUser(); - if (user == null) return; - if (user.defaultShowTime == 999999) { - // This was the old version for infinity -> change it to null - user.defaultShowTime = null; - } - updateUser(user); - await const FlutterSecureStorage().write( - key: SecureStorageKeys.userData, - value: jsonEncode(user), - ); - AppSession.currentUser = user; - }); - - AppSession.triggerUserUpdate(); -} Future deleteLocalUserData() async { final appDir = await getApplicationSupportDirectory(); diff --git a/lib/src/views/components/fingerprint_text.dart b/lib/src/views/components/fingerprint_text.dart deleted file mode 100644 index e470e467..00000000 --- a/lib/src/views/components/fingerprint_text.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; - -class FingerprintText extends StatelessWidget { - const FingerprintText(this.longString, {super.key}); - final String longString; - - String formatString(String input) { - final formattedString = StringBuffer(); - var blockCount = 0; - - for (var i = 0; i < input.length; i += 4) { - final block = input.substring( - i, - i + 4 > input.length ? input.length : i + 4, - ); - formattedString.write(block); - blockCount++; - - if (blockCount == 5) { - formattedString.writeln(); - blockCount = 0; - } else { - formattedString.write(' '); - } - } - - return formattedString.toString().trim(); - } - - @override - Widget build(BuildContext context) { - return SelectableText( - formatString(longString), - style: const TextStyle(fontSize: 16, color: Colors.black), - textAlign: TextAlign.center, - ); - } -} diff --git a/lib/src/views/settings/account.view.dart b/lib/src/views/settings/account.view.dart deleted file mode 100644 index 969db76a..00000000 --- a/lib/src/views/settings/account.view.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'dart:async'; -import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:restart_app/restart_app.dart'; -import 'package:twonly/globals.dart'; -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/storage.dart'; -import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/settings/account/refund_credits.view.dart'; - -class AccountView extends StatefulWidget { - const AccountView({super.key}); - - @override - State createState() => _AccountViewState(); -} - -class _AccountViewState extends State { - String? formattedBallance; - bool hasRemainingBallance = false; - - @override - void initState() { - super.initState(); - unawaited(initAsync()); - } - - Future initAsync() async { - final ballance = await apiService.loadPlanBalance(useCache: false); - if (ballance == null || !mounted) return; - var ballanceInCents = ballance.transactions - .where( - (x) => - x.transactionType != Response_TransactionTypes.ThanksForTesting || - !kReleaseMode, - ) - .map((a) => a.depositCents.toInt()) - .sum; - if (ballanceInCents < 0) { - ballanceInCents = 0; - } - hasRemainingBallance = ballanceInCents > 0; - final myLocale = Localizations.localeOf(context); - formattedBallance = NumberFormat.currency( - locale: myLocale.toString(), - symbol: '€', - decimalDigits: 2, - ).format(ballanceInCents / 100); - setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(context.lang.settingsAccount), - ), - body: ListView( - children: [ - // ListTile( - // title: const Text('Transfer account'), - // subtitle: const Text('Coming soon'), - // onTap: () async { - // await showAlertDialog( - // context, - // 'Coming soon', - // 'This feature is not yet implemented!', - // ); - // }, - // ), - // const Divider(), - Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), - child: const Text( - 'Danger Zone', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 20, - color: Colors.red, - fontWeight: FontWeight.bold, - ), - ), - ), - ListTile( - title: Text( - context.lang.settingsAccountDeleteAccount, - style: const TextStyle(color: Colors.red), - ), - subtitle: (formattedBallance == null) - ? Text(context.lang.settingsAccountDeleteAccountNoInternet) - : hasRemainingBallance - ? Text( - context.lang.settingsAccountDeleteAccountWithBallance( - formattedBallance!, - ), - ) - : Text(context.lang.settingsAccountDeleteAccountNoBallance), - onTap: (formattedBallance == null) - ? null - : () async { - if (hasRemainingBallance) { - final canGoNext = - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return RefundCreditsView( - formattedBalance: formattedBallance!, - ); - }, - ), - ) - as bool?; - unawaited(initAsync()); - if (canGoNext == null || !canGoNext) return; - } - if (!context.mounted) return; - final ok = await showAlertDialog( - context, - context.lang.settingsAccountDeleteModalTitle, - context.lang.settingsAccountDeleteModalBody, - ); - if (ok) { - final res = await apiService.deleteAccount(); - if (res.isError) { - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Could not delete the account. Please ensure you have a internet connection!', - ), - duration: Duration(seconds: 3), - ), - ); - return; - } - await deleteLocalUserData(); - await Restart.restartApp( - notificationTitle: 'Account successfully deleted', - notificationBody: 'Click here to open the app again', - forceKill: true, - ); - } - }, - ), - ], - ), - ); - } -} diff --git a/lib/src/views/settings/account/refund_credits.view.dart b/lib/src/views/settings/account/refund_credits.view.dart deleted file mode 100644 index 2dcd6a55..00000000 --- a/lib/src/views/settings/account/refund_credits.view.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; -// import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/src/views/settings/subscription_custom/voucher.view.dart'; -// import 'package:url_launcher/url_launcher.dart'; - -class RefundCreditsView extends StatefulWidget { - const RefundCreditsView({required this.formattedBalance, super.key}); - final String formattedBalance; - - @override - State createState() => _RefundCreditsViewState(); -} - -class _RefundCreditsViewState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Refund Credits'), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: Text( - 'Remaining balance: ${widget.formattedBalance}', - textAlign: TextAlign.center, - ), - ), - const SizedBox(height: 20), // Space between balance and options - - ListTile( - title: const Text('Create a Voucher'), - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const VoucherView(); - }, - ), - ); - if (context.mounted) { - Navigator.pop(context, false); - } - }, - ), - // ListTile( - // title: Text("Spend to an Open Source Project"), - // onTap: () async {}, - // ), - // ListTile( - // title: Text("Spend to an NGO"), - // onTap: () async {}, - // ), - // ListTile( - // title: Text("Spend to twonly"), - // onTap: () async {}, - // ), - // Divider(), - // ListTile( - // title: Text( - // "Learn more about your donation", - // ), - // subtitle: Text( - // "This will open our webpage which will provide you more informations where we will donate your remaining ballance if you choose this option.", - // ), - // onTap: () { - // launchUrl(Uri.parse("https://twonly.eu/de/donation/")); - // }, - // trailing: FaIcon( - // FontAwesomeIcons.arrowUpRightFromSquare, - // size: 15, - // ), - // ), - ], - ), - ), - ); - } -} diff --git a/lib/src/views/settings/backup/backup.view.dart b/lib/src/views/settings/backup/backup.view.dart deleted file mode 100644 index 5f55ed45..00000000 --- a/lib/src/views/settings/backup/backup.view.dart +++ /dev/null @@ -1,259 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:go_router/go_router.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/constants/routes.keys.dart'; -import 'package:twonly/src/model/json/userdata.dart'; -import 'package:twonly/src/services/backup/create.backup.dart'; -import 'package:twonly/src/utils/misc.dart'; - -class BackupView extends StatefulWidget { - const BackupView({super.key}); - - @override - State createState() => _BackupViewState(); -} - -BackupServer defaultBackupServer = BackupServer( - serverUrl: 'Default', - retentionDays: 180, - maxBackupBytes: 2097152, -); - -class _BackupViewState extends State { - bool isLoading = false; - - int activePageIdx = 0; - - final PageController pageController = PageController(); - - @override - void initState() { - super.initState(); - unawaited(initAsync()); - } - - Future initAsync() async {} - - String backupStatus(LastBackupUploadState status) { - switch (status) { - case LastBackupUploadState.none: - return context.lang.backupPending; - case LastBackupUploadState.pending: - return context.lang.backupPending; - case LastBackupUploadState.failed: - return context.lang.backupFailed; - case LastBackupUploadState.success: - return context.lang.backupSuccess; - } - } - - Future changeTwonlySafePassword() async { - await context.push(Routes.settingsBackupSetup, extra: true); - } - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: AppSession.onUserUpdated, - builder: (context, _) { - final backupServer = - AppSession.currentUser.backupServer ?? defaultBackupServer; - return Scaffold( - appBar: AppBar( - title: Text(context.lang.settingsBackup), - ), - body: PageView( - controller: pageController, - onPageChanged: (index) { - setState(() { - activePageIdx = index; - }); - }, - children: [ - BackupOption( - title: 'twonly Backup', - description: context.lang.backupTwonlySafeDesc, - bottomButton: FilledButton( - onPressed: changeTwonlySafePassword, - child: Text(context.lang.backupChangePassword), - ), - child: (AppSession.currentUser.twonlySafeBackup == null) - ? null - : Column( - children: [ - Table( - defaultVerticalAlignment: - TableCellVerticalAlignment.middle, - children: [ - ...[ - ( - context.lang.backupServer, - (backupServer.serverUrl.contains('@')) - ? backupServer.serverUrl.split('@')[1] - : backupServer.serverUrl.replaceAll( - 'https://', - '', - ), - ), - ( - context.lang.backupMaxBackupSize, - formatBytes(backupServer.maxBackupBytes), - ), - ( - context.lang.backupStorageRetention, - '${backupServer.retentionDays} Days', - ), - ( - context.lang.backupLastBackupDate, - formatDateTime( - context, - AppSession - .currentUser - .twonlySafeBackup! - .lastBackupDone, - ), - ), - ( - context.lang.backupLastBackupSize, - formatBytes( - AppSession - .currentUser - .twonlySafeBackup! - .lastBackupSize, - ), - ), - ( - context.lang.backupLastBackupResult, - backupStatus( - AppSession - .currentUser - .twonlySafeBackup! - .backupUploadState, - ), - ), - ].map((pair) { - return TableRow( - children: [ - TableCell( - // padding: EdgeInsets.all(4), - child: Text(pair.$1), - ), - TableCell( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4, - ), - child: Text( - pair.$2, - textAlign: TextAlign.right, - ), - ), - ), - ], - ); - }), - ], - ), - const SizedBox(height: 10), - OutlinedButton( - onPressed: isLoading - ? null - : () async { - setState(() { - isLoading = true; - }); - await performTwonlySafeBackup(force: true); - setState(() { - isLoading = false; - }); - }, - child: Text(context.lang.backupTwonlySaveNow), - ), - ], - ), - ), - BackupOption( - title: '${context.lang.backupData} (Coming Soon)', - description: context.lang.backupDataDesc, - ), - ], - ), - bottomNavigationBar: BottomNavigationBar( - showSelectedLabels: true, - showUnselectedLabels: true, - unselectedIconTheme: IconThemeData( - color: Theme.of( - context, - ).colorScheme.inverseSurface.withAlpha(150), - ), - selectedIconTheme: IconThemeData( - color: Theme.of(context).colorScheme.inverseSurface, - ), - items: [ - const BottomNavigationBarItem( - icon: FaIcon(FontAwesomeIcons.vault, size: 17), - label: 'twonly Backup', - ), - BottomNavigationBarItem( - icon: const FaIcon(Icons.archive_outlined, size: 17), - label: context.lang.backupData, - ), - ], - onTap: (index) async { - activePageIdx = index; - await pageController.animateToPage( - index, - duration: const Duration(milliseconds: 100), - curve: Curves.bounceIn, - ); - if (mounted) setState(() {}); - }, - currentIndex: activePageIdx, - // ), - ), - ); - }, - ); - } -} - -class BackupOption extends StatelessWidget { - const BackupOption({ - required this.title, - required this.description, - this.bottomButton, - super.key, - this.child, - }); - final String title; - final String description; - final Widget? child; - final Widget? bottomButton; - - @override - Widget build(BuildContext context) { - return Card( - margin: const EdgeInsets.all(16), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Text(description), - const SizedBox(height: 8), - if (child != null) child! else Container(), - Expanded(child: Container()), - if (bottomButton != null) Center(child: bottomButton), - ], - ), - ), - ); - } -} diff --git a/lib/src/views/settings/subscription_custom/checkout.view.dart b/lib/src/views/settings/subscription_custom/checkout.view.dart deleted file mode 100644 index 916c6d87..00000000 --- a/lib/src/views/settings/subscription_custom/checkout.view.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:twonly/src/services/subscription.service.dart'; -import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/settings/subscription_custom/select_payment.view.dart'; -import 'package:twonly/src/views/settings/subscription_custom/subscription.view.dart'; - -class CheckoutView extends StatefulWidget { - const CheckoutView({ - required this.plan, - super.key, - this.disableMonthlyOption, - }); - - final SubscriptionPlan plan; - final bool? disableMonthlyOption; - - @override - State createState() => _CheckoutViewState(); -} - -class _CheckoutViewState extends State { - int checkoutInCents = 0; - bool paidMonthly = false; - bool tryAutoRenewal = true; - - @override - void initState() { - super.initState(); - setCheckout(init: true); - } - - void setCheckout({bool init = false}) { - checkoutInCents = getPlanPrice(widget.plan, paidMonthly: paidMonthly); - if (!init) { - setState(() {}); - } - } - - @override - Widget build(BuildContext context) { - final totalPrice = - '${localePrizing(context, checkoutInCents)}/${paidMonthly ? context.lang.month : context.lang.year}'; - return Scaffold( - appBar: AppBar( - title: Text(context.lang.checkoutOptions), - ), - body: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Expanded( - child: ListView( - children: [ - PlanCard(plan: widget.plan), - if (widget.disableMonthlyOption == null || - !widget.disableMonthlyOption!) - Padding( - padding: const EdgeInsets.all(16), - child: ListTile( - title: Text(context.lang.checkoutPayYearly), - onTap: () { - paidMonthly = !paidMonthly; - setCheckout(); - }, - trailing: Checkbox( - value: !paidMonthly, - onChanged: (a) { - paidMonthly = !paidMonthly; - setCheckout(); - }, - ), - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Card( - color: context.color.surfaceContainer, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - context.lang.checkoutTotal, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - Text( - totalPrice, - textAlign: TextAlign.end, - ), - ], - ), - ), - ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: FilledButton( - onPressed: () async { - final success = - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return SelectPaymentView( - plan: widget.plan, - payMonthly: paidMonthly, - ); - }, - ), - ) - as bool?; - if (success != null && success && context.mounted) { - Navigator.pop(context); - } - }, - child: Text(context.lang.selectPaymentMethod), - ), - ), - const SizedBox(height: 20), - ], - ), - ), - ); - } -} diff --git a/lib/src/views/settings/subscription_custom/manage_subscription.view.dart b/lib/src/views/settings/subscription_custom/manage_subscription.view.dart deleted file mode 100644 index 5e15e9a2..00000000 --- a/lib/src/views/settings/subscription_custom/manage_subscription.view.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:provider/provider.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; -import 'package:twonly/src/providers/purchases.provider.dart'; -import 'package:twonly/src/services/subscription.service.dart'; -import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/settings/subscription_custom/subscription.view.dart'; - -class ManageSubscriptionView extends StatefulWidget { - const ManageSubscriptionView({ - required this.ballance, - required this.nextPayment, - super.key, - }); - - final Response_PlanBallance? ballance; - final DateTime? nextPayment; - - @override - State createState() => _ManageSubscriptionViewState(); -} - -class _ManageSubscriptionViewState extends State { - Response_PlanBallance? ballance; - bool? autoRenewal; - - @override - void initState() { - super.initState(); - ballance = widget.ballance; - if (ballance != null) { - autoRenewal = ballance!.autoRenewal; - } - unawaited(initAsync(true)); - } - - Future initAsync(bool force) async { - if (force) { - ballance = await loadPlanBalance(useCache: false); - if (ballance != null) { - autoRenewal = ballance!.autoRenewal; - } - } - setState(() {}); - } - - Future toggleRenewalOption() async { - final res = await apiService.updatePlanOptions(!autoRenewal!); - if (res.isError) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(errorCodeToText(context, res.error as ErrorCode)), - ), - ); - } - } - await initAsync(true); - } - - @override - Widget build(BuildContext context) { - final plan = context.watch().plan; - final myLocale = Localizations.localeOf(context); - final paidMonthly = ballance?.paymentPeriodDays == MONTHLY_PAYMENT_DAYS; - return Scaffold( - appBar: AppBar( - title: Text(context.lang.manageSubscription), - ), - body: ListView( - children: [ - PlanCard(plan: plan, paidMonthly: paidMonthly), - if (isPayingUser(plan)) const SizedBox(height: 20), - if (widget.nextPayment != null && isPayingUser(plan)) - ListTile( - title: Text( - '${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(widget.nextPayment!)}', - ), - ), - if (autoRenewal != null && isPayingUser(plan)) - ListTile( - title: Text(context.lang.autoRenewal), - subtitle: Text( - context.lang.autoRenewalLongDesc, - style: const TextStyle(fontSize: 12), - ), - onTap: toggleRenewalOption, - trailing: Switch( - value: autoRenewal!, - onChanged: (a) async { - await toggleRenewalOption(); - }, - ), - ), - ], - ), - ); - } -} diff --git a/lib/src/views/settings/subscription_custom/select_payment.view.dart b/lib/src/views/settings/subscription_custom/select_payment.view.dart deleted file mode 100644 index 658eda68..00000000 --- a/lib/src/views/settings/subscription_custom/select_payment.view.dart +++ /dev/null @@ -1,286 +0,0 @@ -import 'dart:async'; - -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/error.pbserver.dart'; -import 'package:twonly/src/services/subscription.service.dart'; -import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/settings/subscription_custom/subscription.view.dart'; -import 'package:twonly/src/views/settings/subscription_custom/voucher.view.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class SelectPaymentView extends StatefulWidget { - const SelectPaymentView({ - super.key, - this.plan, - this.payMonthly, - this.valueInCents, - }); - - final SubscriptionPlan? plan; - final bool? payMonthly; - final int? valueInCents; - - @override - State createState() => _SelectPaymentViewState(); -} - -enum PaymentMethods { - twonlyCredit, - googleSubscription, - appleSubscription, -} - -class _SelectPaymentViewState extends State { - int? balanceInCents; - int checkoutInCents = 0; - bool tryAutoRenewal = true; - - PaymentMethods paymentMethods = PaymentMethods.twonlyCredit; - - @override - void initState() { - super.initState(); - setCheckout(true); - unawaited(initAsync()); - } - - Future initAsync() async { - final balance = await loadPlanBalance(); - if (balance == null) { - balanceInCents = 0; - } else { - balanceInCents = balance.transactions - .map((a) => a.depositCents.toInt()) - .sum; - } - setState(() {}); - } - - void setCheckout(bool init) { - if (widget.valueInCents != null && widget.valueInCents! > 0) { - checkoutInCents = widget.valueInCents!; - } else if (widget.plan != null) { - checkoutInCents = getPlanPrice( - widget.plan!, - paidMonthly: widget.payMonthly!, - ); - } else { - /// Nothing to checkout for... - Navigator.pop(context); - } - - if (!init) { - setState(() {}); - } - } - - @override - Widget build(BuildContext context) { - final totalPrice = (widget.plan != null && widget.payMonthly != null) - ? '${localePrizing(context, checkoutInCents)}/${(widget.payMonthly!) ? context.lang.month : context.lang.year}' - : localePrizing(context, checkoutInCents); - final canPay = - paymentMethods == PaymentMethods.twonlyCredit && - (balanceInCents == null || balanceInCents! >= checkoutInCents); - return Scaffold( - appBar: AppBar( - title: Text(context.lang.selectPaymentMethod), - ), - body: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Container( - decoration: BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.circular(16), - ), - padding: const EdgeInsets.all(16), - child: Text( - context.lang.testPaymentMethod, - textAlign: TextAlign.center, - ), - ), - ), - Expanded( - child: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Card( - color: context.color.surfaceContainer, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(context.lang.twonlyCredit), - if (balanceInCents != null) - Text( - '${context.lang.currentBalance}: ${localePrizing(context, balanceInCents!)}', - style: const TextStyle(fontSize: 10), - ), - ], - ), - Checkbox( - value: - paymentMethods == PaymentMethods.twonlyCredit, - onChanged: (value) { - setState(() { - paymentMethods = PaymentMethods.twonlyCredit; - }); - }, - ), - ], - ), - ), - ), - ), - ], - ), - ), - if (!canPay) ...[ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - context.lang.notEnoughCredit, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 12), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: FilledButton( - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const VoucherView(); - }, - ), - ); - await initAsync(); - }, - child: Text(context.lang.chargeCredit), - ), - ), - ], - Padding( - padding: const EdgeInsets.all(16), - child: ListTile( - title: Text(context.lang.autoRenewal), - subtitle: Text(context.lang.autoRenewalDesc), - onTap: () { - tryAutoRenewal = !tryAutoRenewal; - setCheckout(false); - }, - trailing: Checkbox( - value: tryAutoRenewal, - onChanged: (a) { - tryAutoRenewal = !tryAutoRenewal; - setCheckout(false); - }, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Card( - color: context.color.surfaceContainer, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - context.lang.checkoutTotal, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - Text( - totalPrice, - textAlign: TextAlign.end, - ), - ], - ), - ), - ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: FilledButton( - onPressed: canPay - ? () async { - final res = await apiService.switchToPayedPlan( - widget.plan!.name, - widget.payMonthly!, - tryAutoRenewal, - ); - if (!context.mounted) return; - if (res.isSuccess) { - await updateUsersPlan(context, widget.plan!); - if (!context.mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.lang.planSuccessUpgraded), - ), - ); - Navigator.of(context).pop(true); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - errorCodeToText( - context, - res.error as ErrorCode, - ), - ), - ), - ); - } - } - : null, - child: Text(context.lang.checkoutSubmit), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - TextButton( - onPressed: () => launchUrl( - Uri.parse( - 'https://twonly.eu/de/legal/#revocation-policy', - ), - ), - child: const Text( - 'Widerrufsbelehrung', - style: TextStyle(color: Colors.blue), - ), - ), - TextButton( - onPressed: () => launchUrl( - Uri.parse('https://twonly.eu/de/legal/agb.html'), - ), - child: const Text( - 'ABG', - style: TextStyle(color: Colors.blue), - ), - ), - ], - ), - const SizedBox(height: 20), - ], - ), - ), - ); - } -} diff --git a/lib/src/views/settings/subscription_custom/subscription.view.dart b/lib/src/views/settings/subscription_custom/subscription.view.dart deleted file mode 100644 index 062b22a0..00000000 --- a/lib/src/views/settings/subscription_custom/subscription.view.dart +++ /dev/null @@ -1,473 +0,0 @@ -// ignore_for_file: inference_failure_on_instance_creation -import 'dart:async'; -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:intl/intl.dart'; -import 'package:provider/provider.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; -import 'package:twonly/src/providers/purchases.provider.dart'; -import 'package:twonly/src/services/subscription.service.dart'; -import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/components/better_list_title.dart'; -import 'package:twonly/src/views/settings/subscription/additional_users.view.dart'; -import 'package:twonly/src/views/settings/subscription_custom/checkout.view.dart'; -import 'package:twonly/src/views/settings/subscription_custom/manage_subscription.view.dart'; -import 'package:twonly/src/views/settings/subscription_custom/transaction.view.dart'; -import 'package:twonly/src/views/settings/subscription_custom/voucher.view.dart'; - -String localePrizing(BuildContext context, int cents) { - final myLocale = Localizations.localeOf(context); - final euros = cents / 100; - - if (euros == euros.toInt()) { - return '${euros.toInt()}€'; - } - - return NumberFormat.currency( - locale: myLocale.toString(), - symbol: '€', - decimalDigits: 2, - ).format(cents / 100); -} - -Future loadPlanBalance({bool useCache = true}) async { - final ballance = await apiService.getPlanBallance(); - if (ballance != null) { - await updateUser((u) => u.lastPlanBallance = ballance.writeToJson()); - return ballance; - } - final user = await getUser(); - if (user != null && user.lastPlanBallance != null && useCache) { - try { - return Response_PlanBallance.fromJson( - user.lastPlanBallance!, - ); - } catch (e) { - Log.error('from json: $e'); - } - } - return ballance; -} - -// ignore: constant_identifier_names -const int MONTHLY_PAYMENT_DAYS = 30; -// ignore: constant_identifier_names -const int YEARLY_PAYMENT_DAYS = 365; - -class SubscriptionCustomView extends StatefulWidget { - const SubscriptionCustomView({super.key, this.redirectError}); - - final ErrorCode? redirectError; - - @override - State createState() => _SubscriptionCustomViewState(); -} - -class _SubscriptionCustomViewState extends State { - bool loaded = false; - bool testerRequested = true; - Response_PlanBallance? ballance; - String? additionalOwnerName; - - @override - void initState() { - super.initState(); - unawaited(initAsync()); - } - - Future initAsync() async { - ballance = await loadPlanBalance(); - if (ballance != null && ballance!.hasAdditionalAccountOwnerId()) { - final ownerId = ballance!.additionalAccountOwnerId.toInt(); - final contact = await twonlyDB.contactsDao - .getContactByUserId(ownerId) - .getSingleOrNull(); - if (contact != null) { - additionalOwnerName = getContactDisplayName(contact); - } else { - additionalOwnerName = ownerId.toString(); - } - } - setState(() {}); - } - - @override - Widget build(BuildContext context) { - final myLocale = Localizations.localeOf(context); - String? formattedBalance; - DateTime? nextPayment; - final currentPlan = context.watch().plan; - - if (ballance != null) { - final lastPaymentDateTime = DateTime.fromMillisecondsSinceEpoch( - ballance!.lastPaymentDoneUnixTimestamp.toInt() * 1000, - ); - if (isPayingUser(currentPlan)) { - nextPayment = lastPaymentDateTime.add( - Duration(days: ballance!.paymentPeriodDays.toInt()), - ); - } - final ballanceInCents = ballance!.transactions - .map((a) => a.depositCents.toInt()) - .sum; - formattedBalance = NumberFormat.currency( - locale: myLocale.toString(), - symbol: '€', - decimalDigits: 2, - ).format(ballanceInCents / 100); - } - - return Scaffold( - appBar: AppBar( - title: Text(context.lang.settingsSubscription), - ), - body: ListView( - children: [ - if (widget.redirectError != null) - Center( - child: Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.orangeAccent, - borderRadius: BorderRadius.circular(15), - ), - child: Text( - (widget.redirectError == ErrorCode.PlanLimitReached) - ? context.lang.planLimitReached - : context.lang.planNotAllowed, - style: const TextStyle(color: Colors.black), - textAlign: TextAlign.center, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(32), - child: Center( - child: Container( - decoration: BoxDecoration( - color: context.color.primary, - borderRadius: BorderRadius.circular(15), - ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - child: Text( - currentPlan.name, - style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: isDarkMode(context) ? Colors.black : Colors.white, - ), - ), - ), - ), - ), - if (additionalOwnerName != null) - Center( - child: Text( - context.lang.partOfPaidPlanOf(additionalOwnerName!), - textAlign: TextAlign.center, - style: const TextStyle(color: Colors.orange), - ), - ), - if (!isPayingUser(currentPlan)) - Center( - child: Padding( - padding: const EdgeInsets.all(18), - child: Text( - context.lang.upgradeToPaidPlan, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 18), - ), - ), - ), - if (!isPayingUser(currentPlan) || - currentPlan == SubscriptionPlan.Tester) - PlanCard( - plan: SubscriptionPlan.Pro, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const CheckoutView( - plan: SubscriptionPlan.Pro, - ); - }, - ), - ); - await initAsync(); - }, - ), - if (currentPlan != SubscriptionPlan.Family) - PlanCard( - plan: SubscriptionPlan.Family, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return CheckoutView( - plan: SubscriptionPlan.Family, - disableMonthlyOption: - currentPlan == SubscriptionPlan.Pro && - ballance!.paymentPeriodDays.toInt() == - YEARLY_PAYMENT_DAYS, - ); - }, - ), - ); - await initAsync(); - }, - ), - const SizedBox(height: 10), - if (currentPlan != SubscriptionPlan.Family) const Divider(), - BetterListTile( - icon: FontAwesomeIcons.gears, - text: context.lang.manageSubscription, - subtitle: (nextPayment != null) - ? Text( - '${context.lang.nextPayment}: ${DateFormat.yMMMMd(myLocale.toString()).format(nextPayment)}', - ) - : null, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return ManageSubscriptionView( - ballance: ballance, - nextPayment: nextPayment, - ); - }, - ), - ); - await initAsync(); - }, - ), - BetterListTile( - icon: FontAwesomeIcons.moneyBillTransfer, - text: context.lang.transactionHistory, - subtitle: (formattedBalance != null) - ? Text('${context.lang.currentBalance}: $formattedBalance') - : null, - onTap: () async { - if (formattedBalance == null) return; - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return TransactionView( - transactions: ballance?.transactions, - formattedBalance: formattedBalance!, - ); - }, - ), - ); - }, - ), - if (isPayingUser(currentPlan) || - currentPlan == SubscriptionPlan.Tester) - BetterListTile( - icon: FontAwesomeIcons.userPlus, - text: context.lang.manageAdditionalUsers, - subtitle: loaded ? Text('${context.lang.open}: 3') : null, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return AdditionalUsersView( - ballance: ballance, - ); - }, - ), - ); - await initAsync(); - }, - ), - BetterListTile( - icon: FontAwesomeIcons.ticket, - text: context.lang.createOrRedeemVoucher, - onTap: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const VoucherView(); - }, - ), - ); - await initAsync(); - }, - ), - const SizedBox(height: 30), - ], - ), - ); - } -} - -int getPlanPrice(SubscriptionPlan plan, {required bool paidMonthly}) { - switch (plan) { - case SubscriptionPlan.Pro: - return paidMonthly ? 100 : 1000; - case SubscriptionPlan.Family: - return paidMonthly ? 200 : 2000; - // ignore: no_default_cases - default: - return 0; - } -} - -class PlanCard extends StatelessWidget { - const PlanCard({ - required this.plan, - super.key, - this.refund, - this.onTap, - this.paidMonthly, - }); - final SubscriptionPlan plan; - final void Function()? onTap; - final int? refund; - final bool? paidMonthly; - - @override - Widget build(BuildContext context) { - final yearlyPrice = getPlanPrice(plan, paidMonthly: false); - final monthlyPrice = getPlanPrice(plan, paidMonthly: true); - var features = []; - - switch (plan.name) { - case 'Free': - features = [context.lang.freeFeature1]; - case 'Plus': - features = [context.lang.plusFeature1, context.lang.plusFeature2]; - case 'Tester': - case 'Pro': - features = [ - context.lang.proFeature1, - context.lang.proFeature2, - context.lang.proFeature3, - context.lang.proFeature4, - ]; - case 'Family': - features = [ - context.lang.proFeature1, - context.lang.familyFeature2, - context.lang.proFeature3, - context.lang.proFeature4, - ]; - default: - } - - return Padding( - padding: const EdgeInsets.only(left: 16, right: 16), - child: GestureDetector( - onTap: onTap, - child: Card( - elevation: 4, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - color: context.color.surfaceContainer, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Text( - plan.name, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - if (yearlyPrice != 0) const SizedBox(height: 10), - if (yearlyPrice != 0 && paidMonthly == null) - Column( - children: [ - if (paidMonthly == null || paidMonthly!) - Text( - '${localePrizing(context, yearlyPrice)}/${context.lang.year}', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - if (paidMonthly == null || !paidMonthly!) - Text( - '${localePrizing(context, monthlyPrice)}/${context.lang.month}', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - color: Colors.grey, - ), - ), - ], - ), - if (paidMonthly != null) - Text( - (paidMonthly!) - ? '${localePrizing(context, monthlyPrice)}/${context.lang.month}' - : '${localePrizing(context, yearlyPrice)}/${context.lang.year}', - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 10), - ...features.map( - (feature) => Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Text( - feature, - textAlign: TextAlign.center, - ), - ), - ), - if (refund != null && refund! > 0) - Padding( - padding: const EdgeInsets.only(top: 7), - child: Text( - context.lang.subscriptionRefund( - localePrizing(context, refund!), - ), - textAlign: TextAlign.center, - style: TextStyle( - color: context.color.primary, - fontSize: 12, - ), - ), - ), - if (onTap != null) - Padding( - padding: const EdgeInsets.only(top: 10), - child: FilledButton.icon( - onPressed: onTap, - label: Text( - context.lang.upgradeToPaidPlanButton(plan.name, ''), - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/src/views/settings/subscription_custom/transaction.view.dart b/lib/src/views/settings/subscription_custom/transaction.view.dart deleted file mode 100644 index 70a597b8..00000000 --- a/lib/src/views/settings/subscription_custom/transaction.view.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; -import 'package:twonly/src/utils/misc.dart'; - -class TransactionView extends StatefulWidget { - const TransactionView({ - required this.transactions, - required this.formattedBalance, - super.key, - }); - final List? transactions; - final String formattedBalance; - - @override - State createState() => _TransactionViewState(); -} - -class _TransactionViewState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(context.lang.transactionHistory), - ), - body: ListView( - children: [ - Padding( - padding: const EdgeInsets.all(32), - child: Center( - child: Container( - decoration: BoxDecoration( - color: context.color.primary, - borderRadius: BorderRadius.circular(15), - ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - child: Text( - widget.formattedBalance, - style: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: isDarkMode(context) ? Colors.black : Colors.white, - ), - ), - ), - ), - ), - if (widget.transactions != null) - ...widget.transactions!.map((x) => TransactionCard(transaction: x)), - ], - ), - ); - } -} - -class TransactionCard extends StatefulWidget { - const TransactionCard({required this.transaction, super.key}); - final Response_Transaction transaction; - - @override - State createState() => _TransactionCardState(); -} - -class _TransactionCardState extends State { - String typeToText(Response_TransactionTypes type) { - switch (type) { - case Response_TransactionTypes.Cash: - return context.lang.transactionCash; - case Response_TransactionTypes.PlanUpgrade: - return context.lang.transactionPlanUpgrade; - case Response_TransactionTypes.Refund: - return context.lang.transactionRefund; - case Response_TransactionTypes.ThanksForTesting: - return context.lang.transactionThanksForTesting; - case Response_TransactionTypes.Unknown: - return context.lang.transactionUnknown; - case Response_TransactionTypes.VoucherCreated: - return context.lang.transactionVoucherCreated; - case Response_TransactionTypes.VoucherRedeemed: - return context.lang.transactionVoucherRedeemed; - case Response_TransactionTypes.AutoRenewal: - return context.lang.transactionAutoRenewal; - } - return type.toString(); - } - - @override - Widget build(BuildContext context) { - final myLocale = Localizations.localeOf(context); - final formattedValue = NumberFormat.currency( - locale: myLocale.toString(), - symbol: '€', - decimalDigits: 2, - ).format(widget.transaction.depositCents.toInt() / 100); - - final timestamp = DateTime.fromMillisecondsSinceEpoch( - widget.transaction.createdAtUnixTimestamp.toInt() * 1000, - ); - - return Card( - margin: const EdgeInsets.all(10), - elevation: 5, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - typeToText(widget.transaction.transactionType), - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - Text( - DateFormat.yMMMMd(myLocale.toString()).format(timestamp), - style: const TextStyle( - fontSize: 12, - color: Colors.grey, - ), - ), - ], - ), - Text( - formattedValue, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: (widget.transaction.depositCents < 0) - ? Colors.red - : context.color.primary, - ), - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/src/views/settings/subscription_custom/voucher.view.dart b/lib/src/views/settings/subscription_custom/voucher.view.dart deleted file mode 100644 index 770da2f0..00000000 --- a/lib/src/views/settings/subscription_custom/voucher.view.dart +++ /dev/null @@ -1,321 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:intl/intl.dart'; -import 'package:twonly/globals.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'; -import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; -import 'package:twonly/src/utils/misc.dart'; - -class VoucherView extends StatefulWidget { - const VoucherView({super.key}); - - @override - State createState() => _VoucherViewState(); -} - -class _VoucherViewState extends State { - List vouchers = []; - - @override - void initState() { - super.initState(); - unawaited(initAsync()); - } - - Future initAsync() async { - final resVouchers = await apiService.getVoucherList(); - setState(() { - vouchers = resVouchers?.vouchers ?? []; - }); - } - - @override - Widget build(BuildContext context) { - final openVoucher = vouchers.where((x) => !x.redeemed && !x.requested); - final redeemedVoucher = vouchers.where((x) => x.redeemed); - return Scaffold( - appBar: AppBar( - title: Text(context.lang.createOrRedeemVoucher), - ), - body: ListView( - children: [ - ListTile( - title: Text(context.lang.redeemVoucher), - onTap: () async { - await redeemVoucher(context); - await initAsync(); - }, - ), - ListTile( - title: Text(context.lang.createVoucher), - onTap: () async { - await showBuyVoucher(context); - await initAsync(); - }, - ), - const Divider(), - if (openVoucher.isNotEmpty) - ListTile( - title: Text( - context.lang.openVouchers, - style: const TextStyle(fontSize: 13), - ), - ), - ...openVoucher.map((x) => VoucherCard(voucher: x)), - if (redeemedVoucher.isNotEmpty) - ListTile( - title: Text( - context.lang.redeemedVouchers, - style: const TextStyle(fontSize: 13), - ), - ), - ...redeemedVoucher.map((x) => VoucherCard(voucher: x)), - ], - ), - ); - } -} - -class VoucherCard extends StatefulWidget { - const VoucherCard({required this.voucher, super.key}); - final Response_Voucher voucher; - - @override - State createState() => _VoucherCardState(); -} - -class _VoucherCardState extends State { - Future _copyVoucherId() async { - if (!widget.voucher.redeemed) { - await Clipboard.setData(ClipboardData(text: widget.voucher.voucherId)); - await HapticFeedback.heavyImpact(); - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('${widget.voucher.voucherId} copied.')), - ); - } - } - - @override - Widget build(BuildContext context) { - final isRedeemed = widget.voucher.redeemed || widget.voucher.requested; - final myLocale = Localizations.localeOf(context); - final formattedValue = NumberFormat.currency( - locale: myLocale.toString(), - symbol: '€', - decimalDigits: 2, - ).format(widget.voucher.valueCents.toInt() / 100); - - return GestureDetector( - onTap: _copyVoucherId, - child: Card( - margin: const EdgeInsets.all(10), - elevation: 5, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - widget.voucher.voucherId.toUpperCase(), - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: isRedeemed ? Colors.grey : context.color.onSurface, - ), - ), - Text( - formattedValue, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: isRedeemed ? Colors.grey : context.color.onSurface, - ), - ), - ], - ), - ], - ), - ), - ), - ); - } -} - -Future redeemVoucher(BuildContext context) async { - var voucherCode = ''; - // - // ignore: inference_failure_on_function_invocation - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(context.lang.redeemVoucher), - content: StatefulBuilder( - builder: (context, setState) { - return SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 16), - child: TextField( - onChanged: (value) { - // Convert to uppercase - setState(() { - voucherCode = value.toUpperCase(); - }); - }, - decoration: InputDecoration( - labelText: context.lang.enterVoucherCode, - border: const OutlineInputBorder(), - ), - // Set the text to be uppercase - textCapitalization: TextCapitalization.characters, - ), - ), - ], - ), - ); - }, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(context.lang.cancel), - ), - TextButton( - onPressed: () async { - final res = await apiService.redeemVoucher(voucherCode); - if (!context.mounted) return; - if (res.isSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.lang.voucherRedeemed)), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - errorCodeToText( - context, - res.error as ErrorCode, - ), - ), - ), - ); - } - Navigator.of(context).pop(); - }, - child: Text(context.lang.ok), - ), - ], - ); - }, - ); -} - -Future showBuyVoucher(BuildContext context) async { - var quantity = 1000; - // - // ignore: inference_failure_on_function_invocation - await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(context.lang.createVoucher), - content: StatefulBuilder( - builder: (context, setState) { - return SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(context.lang.createVoucherDesc), - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - icon: const Icon(Icons.remove), - onPressed: () { - if (quantity > 1) { - setState(() { - if (quantity <= 100) return; - if (quantity <= 1000) { - quantity -= 100; - } else { - quantity -= 500; - } - }); - } - }, - ), - Text( - NumberFormat.currency( - locale: Localizations.localeOf(context).toString(), - symbol: '€', - decimalDigits: 2, - ).format(quantity / 100), - style: const TextStyle(fontSize: 24), - ), - IconButton( - icon: const Icon(Icons.add), - onPressed: () { - setState(() { - if (quantity >= 1000) { - quantity += 500; - } else { - quantity += 100; - } - }); - }, - ), - ], - ), - ], - ), - ); - }, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); // Close the dialog - }, - child: Text(context.lang.cancel), - ), - TextButton( - onPressed: () async { - final res = await apiService.buyVoucher(quantity); - if (!context.mounted) return; - if (res.isSuccess) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.lang.voucherCreated)), - ); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - errorCodeToText( - context, - res.error as ErrorCode, - ), - ), - ), - ); - } - Navigator.of(context).pop(); // Close the dialog - }, - child: Text(context.lang.buy), - ), - ], - ); - }, - ); -} diff --git a/lib/src/views/components/alert_dialog.dart b/lib/src/visual/components/alert.dialog.dart similarity index 100% rename from lib/src/views/components/alert_dialog.dart rename to lib/src/visual/components/alert.dialog.dart diff --git a/lib/src/views/components/animate_icon.dart b/lib/src/visual/components/animate_icon.comp.dart similarity index 98% rename from lib/src/views/components/animate_icon.dart rename to lib/src/visual/components/animate_icon.comp.dart index 055f00ce..9f34c5e9 100644 --- a/lib/src/views/components/animate_icon.dart +++ b/lib/src/visual/components/animate_icon.comp.dart @@ -17,8 +17,12 @@ bool isOneEmoji(String character) { return false; } -class EmojiAnimation extends StatelessWidget { - const EmojiAnimation({required this.emoji, super.key, this.repeat = true}); +class EmojiAnimationComp extends StatelessWidget { + const EmojiAnimationComp({ + required this.emoji, + super.key, + this.repeat = true, + }); final String emoji; final bool repeat; static final Map animatedIcons = { @@ -279,7 +283,7 @@ class EmojiAnimationFlying extends StatelessWidget { padding: EdgeInsets.only(bottom: 20 * value), child: SizedBox( width: size + 30 * value, - child: EmojiAnimation(emoji: emoji), + child: EmojiAnimationComp(emoji: emoji), ), ); }, diff --git a/lib/src/views/components/app_outdated.dart b/lib/src/visual/components/app_outdated.comp.dart similarity index 94% rename from lib/src/views/components/app_outdated.dart rename to lib/src/visual/components/app_outdated.comp.dart index efa2aac5..e3d172e5 100644 --- a/lib/src/views/components/app_outdated.dart +++ b/lib/src/visual/components/app_outdated.comp.dart @@ -1,23 +1,24 @@ import 'dart:async'; import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:url_launcher/url_launcher.dart'; -class AppOutdated extends StatefulWidget { - const AppOutdated({super.key}); +class AppOutdatedComp extends StatefulWidget { + const AppOutdatedComp({super.key}); @override - State createState() => _AppOutdatedState(); + State createState() => _AppOutdatedCompState(); } -class _AppOutdatedState extends State { +class _AppOutdatedCompState extends State { bool appIsOutdated = false; bool newDeviceRegistered = false; - + late StreamSubscription _subOutdated; late StreamSubscription _subNewDevice; diff --git a/lib/src/views/components/avatar_icon.component.dart b/lib/src/visual/components/avatar_icon.comp.dart similarity index 93% rename from lib/src/views/components/avatar_icon.component.dart rename to lib/src/visual/components/avatar_icon.comp.dart index 122f2e9d..5580e369 100644 --- a/lib/src/views/components/avatar_icon.component.dart +++ b/lib/src/visual/components/avatar_icon.comp.dart @@ -1,10 +1,10 @@ import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/avatars.dart'; -import 'package:twonly/src/utils/misc.dart'; import 'package:vector_graphics/vector_graphics.dart'; class AvatarIcon extends StatefulWidget { @@ -93,19 +93,19 @@ class _AvatarIconState extends State { setState(() {}); }); } else if (widget.myAvatar) { - _userSub = AppSession.onUserUpdated.listen((_) { + _userSub = appSession.onUserUpdated.listen((_) { if (mounted) { setState(() { - if (AppSession.currentUser.avatarSvg != null) { - _avatarSvg = AppSession.currentUser.avatarSvg; + if (appSession.currentUser.avatarSvg != null) { + _avatarSvg = appSession.currentUser.avatarSvg; } else { _avatarContacts = []; } }); } }); - if (AppSession.currentUser.avatarSvg != null) { - _avatarSvg = AppSession.currentUser.avatarSvg; + if (appSession.currentUser.avatarSvg != null) { + _avatarSvg = appSession.currentUser.avatarSvg; } } else if (widget.contactId != null) { contactStream = twonlyDB.contactsDao diff --git a/lib/src/views/components/connection_status_badge.dart b/lib/src/visual/components/connection_status.comp.dart similarity index 86% rename from lib/src/views/components/connection_status_badge.dart rename to lib/src/visual/components/connection_status.comp.dart index dba83d11..19ff05f2 100644 --- a/lib/src/views/components/connection_status_badge.dart +++ b/lib/src/visual/components/connection_status.comp.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:twonly/src/providers/connection.provider.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/components/loader/ripple.loader.dart'; +import 'package:twonly/src/visual/loader/ripple.loader.dart'; -class ConnectionStatusBadge extends StatelessWidget { - const ConnectionStatusBadge({ +class ConnectionStatusComp extends StatelessWidget { + const ConnectionStatusComp({ required this.child, super.key, }); diff --git a/lib/src/views/components/user_context_menu.component.dart b/lib/src/visual/components/context_menu/user.context_menu.dart similarity index 91% rename from lib/src/views/components/user_context_menu.component.dart rename to lib/src/visual/components/context_menu/user.context_menu.dart index 6471bd9d..4d8bfbc9 100644 --- a/lib/src/views/components/user_context_menu.component.dart +++ b/lib/src/visual/components/context_menu/user.context_menu.dart @@ -4,7 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/components/context_menu.component.dart'; +import 'package:twonly/src/visual/context_menu/context_menu.helper.dart'; class UserContextMenu extends StatelessWidget { const UserContextMenu({ diff --git a/lib/src/views/components/emoji_picker.bottom.dart b/lib/src/visual/components/emoji_picker.bottom.dart similarity index 97% rename from lib/src/views/components/emoji_picker.bottom.dart rename to lib/src/visual/components/emoji_picker.bottom.dart index ba2e660c..1fa65465 100755 --- a/lib/src/views/components/emoji_picker.bottom.dart +++ b/lib/src/visual/components/emoji_picker.bottom.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:flutter/material.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart'; class EmojiPickerBottom extends StatelessWidget { const EmojiPickerBottom({super.key}); diff --git a/lib/src/views/components/flame.dart b/lib/src/visual/components/flame_counter.comp.dart similarity index 90% rename from lib/src/views/components/flame.dart rename to lib/src/visual/components/flame_counter.comp.dart index 1ea1ff17..5a629e31 100644 --- a/lib/src/views/components/flame.dart +++ b/lib/src/visual/components/flame_counter.comp.dart @@ -1,8 +1,9 @@ import 'dart:async'; + import 'package:flutter/material.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/views/components/animate_icon.dart'; +import 'package:twonly/src/visual/components/animate_icon.comp.dart'; class FlameCounterWidget extends StatefulWidget { const FlameCounterWidget({ @@ -48,7 +49,8 @@ class _FlameCounterWidgetState extends State { } if (groupId != null && group != null) { isBestFriend = - AppSession.currentUser.myBestFriendGroupId == groupId && group.alsoBestFriend; + appSession.currentUser.myBestFriendGroupId == groupId && + group.alsoBestFriend; final stream = twonlyDB.groupsDao.watchFlameCounter(groupId); flameCounterSub = stream.listen((counter) { if (mounted) { @@ -85,7 +87,7 @@ class _FlameCounterWidgetState extends State { ), SizedBox( height: 15, - child: EmojiAnimation( + child: EmojiAnimationComp( emoji: flameEmoji, ), ), diff --git a/lib/src/views/components/notification_badge.dart b/lib/src/visual/components/notification_badge.comp.dart similarity index 93% rename from lib/src/views/components/notification_badge.dart rename to lib/src/visual/components/notification_badge.comp.dart index a75ded0a..2e4b7617 100644 --- a/lib/src/views/components/notification_badge.dart +++ b/lib/src/visual/components/notification_badge.comp.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class NotificationBadge extends StatelessWidget { - const NotificationBadge({ +class NotificationBadgeComp extends StatelessWidget { + const NotificationBadgeComp({ required this.count, required this.child, this.backgroundColor = Colors.red, diff --git a/lib/src/views/components/select_chat_deletion_time.comp.dart b/lib/src/visual/components/select_chat_deletion_time.comp.dart similarity index 95% rename from lib/src/views/components/select_chat_deletion_time.comp.dart rename to lib/src/visual/components/select_chat_deletion_time.comp.dart index 58602c64..17534a80 100644 --- a/lib/src/views/components/select_chat_deletion_time.comp.dart +++ b/lib/src/visual/components/select_chat_deletion_time.comp.dart @@ -4,15 +4,15 @@ import 'package:drift/drift.dart' show Value; import 'package:fixnum/fixnum.dart'; import 'package:flutter/cupertino.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart'; -import 'package:twonly/src/services/api/messages.dart'; +import 'package:twonly/src/services/api/messages.api.dart'; import 'package:twonly/src/services/group.services.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/components/better_list_title.dart'; -import 'package:twonly/src/views/groups/group.view.dart'; +import 'package:twonly/src/visual/elements/better_list_title.element.dart'; +import 'package:twonly/src/visual/views/groups/group.view.dart'; class SelectChatDeletionTimeListTitle extends StatefulWidget { const SelectChatDeletionTimeListTitle({ diff --git a/lib/src/views/components/verified_shield.dart b/lib/src/visual/components/verification_badge.comp.dart similarity index 85% rename from lib/src/views/components/verified_shield.dart rename to lib/src/visual/components/verification_badge.comp.dart index 293e0380..847ca967 100644 --- a/lib/src/views/components/verified_shield.dart +++ b/lib/src/visual/components/verification_badge.comp.dart @@ -1,13 +1,14 @@ import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/views/components/svg_icon.dart'; +import 'package:twonly/src/visual/elements/svg_icon.element.dart'; -class VerifiedShield extends StatefulWidget { - const VerifiedShield({ +class VerificationBadgeComp extends StatefulWidget { + const VerificationBadgeComp({ this.contact, this.group, super.key, @@ -23,10 +24,10 @@ class VerifiedShield extends StatefulWidget { final bool clickable; @override - State createState() => _VerifiedShieldState(); + State createState() => _VerificationBadgeCompState(); } -class _VerifiedShieldState extends State { +class _VerificationBadgeCompState extends State { bool isVerified = false; Contact? contact; diff --git a/lib/src/views/components/context_menu.component.dart b/lib/src/visual/context_menu/context_menu.helper.dart similarity index 100% rename from lib/src/views/components/context_menu.component.dart rename to lib/src/visual/context_menu/context_menu.helper.dart diff --git a/lib/src/views/components/group_context_menu.component.dart b/lib/src/visual/context_menu/group.context_menu.dart similarity index 94% rename from lib/src/views/components/group_context_menu.component.dart rename to lib/src/visual/context_menu/group.context_menu.dart index 08b66795..0e86fc59 100644 --- a/lib/src/views/components/group_context_menu.component.dart +++ b/lib/src/visual/context_menu/group.context_menu.dart @@ -2,12 +2,12 @@ import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/components/alert_dialog.dart'; -import 'package:twonly/src/views/components/context_menu.component.dart'; +import 'package:twonly/src/visual/components/alert.dialog.dart'; +import 'package:twonly/src/visual/context_menu/context_menu.helper.dart'; class GroupContextMenu extends StatelessWidget { const GroupContextMenu({ diff --git a/lib/src/visual/context_menu/user.context_menu.dart b/lib/src/visual/context_menu/user.context_menu.dart new file mode 100644 index 00000000..4d8bfbc9 --- /dev/null +++ b/lib/src/visual/context_menu/user.context_menu.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:go_router/go_router.dart'; +import 'package:twonly/src/constants/routes.keys.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/context_menu/context_menu.helper.dart'; + +class UserContextMenu extends StatelessWidget { + const UserContextMenu({ + required this.contact, + required this.child, + super.key, + }); + final Widget child; + final Contact contact; + + @override + Widget build(BuildContext context) { + return ContextMenu( + items: [ + ContextMenuItem( + title: context.lang.contextMenuUserProfile, + onTap: () => context.push(Routes.profileContact(contact.userId)), + icon: FontAwesomeIcons.user, + ), + ], + child: child, + ); + } +} diff --git a/lib/src/visual/decorations/input_text.decoration.dart b/lib/src/visual/decorations/input_text.decoration.dart new file mode 100644 index 00000000..9a42f916 --- /dev/null +++ b/lib/src/visual/decorations/input_text.decoration.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +InputDecoration inputTextMessageDeco(BuildContext context, String hintText) { + return InputDecoration( + hintText: hintText, + contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 2, + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: const BorderSide(color: Colors.grey, width: 2), + ), + ); +} + +InputDecoration getInputDecoration(BuildContext context, String hintText) { + final primaryColor = Theme.of(context).colorScheme.primary; + return InputDecoration( + hintText: hintText, + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(9), + borderSide: BorderSide(color: primaryColor), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide(color: Theme.of(context).colorScheme.outline), + ), + contentPadding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), + ); +} diff --git a/lib/src/views/components/better_list_title.dart b/lib/src/visual/elements/better_list_title.element.dart similarity index 100% rename from lib/src/views/components/better_list_title.dart rename to lib/src/visual/elements/better_list_title.element.dart diff --git a/lib/src/views/components/better_text.dart b/lib/src/visual/elements/better_text.element.dart similarity index 100% rename from lib/src/views/components/better_text.dart rename to lib/src/visual/elements/better_text.element.dart diff --git a/lib/src/views/components/headline.dart b/lib/src/visual/elements/headline.element.dart similarity index 77% rename from lib/src/views/components/headline.dart rename to lib/src/visual/elements/headline.element.dart index f21c5ae3..0b8a626a 100644 --- a/lib/src/views/components/headline.dart +++ b/lib/src/visual/elements/headline.element.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class HeadLineComponent extends StatelessWidget { - const HeadLineComponent(this.text, {super.key}); +class HeadLineComp extends StatelessWidget { + const HeadLineComp(this.text, {super.key}); final String text; @override diff --git a/lib/src/views/components/radio_button.dart b/lib/src/visual/elements/radio_button.element.dart similarity index 100% rename from lib/src/views/components/radio_button.dart rename to lib/src/visual/elements/radio_button.element.dart diff --git a/lib/src/views/components/svg_icon.dart b/lib/src/visual/elements/svg_icon.element.dart similarity index 100% rename from lib/src/views/components/svg_icon.dart rename to lib/src/visual/elements/svg_icon.element.dart diff --git a/lib/src/views/components/media_view_sizing.dart b/lib/src/visual/helpers/media_view_sizing.helper.dart similarity index 90% rename from lib/src/views/components/media_view_sizing.dart rename to lib/src/visual/helpers/media_view_sizing.helper.dart index f40491d9..001f90cb 100644 --- a/lib/src/views/components/media_view_sizing.dart +++ b/lib/src/visual/helpers/media_view_sizing.helper.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class MediaViewSizing extends StatefulWidget { - const MediaViewSizing({ +class MediaViewSizingHelper extends StatefulWidget { + const MediaViewSizingHelper({ required this.child, super.key, this.requiredHeight, @@ -15,10 +15,10 @@ class MediaViewSizing extends StatefulWidget { final Widget child; @override - State createState() => _MediaViewSizingState(); + State createState() => _MediaViewSizingHelperState(); } -class _MediaViewSizingState extends State { +class _MediaViewSizingHelperState extends State { @override Widget build(BuildContext context) { var needToDownSizeImage = false; diff --git a/lib/src/utils/screenshot.dart b/lib/src/visual/helpers/screenshot.helper.dart similarity index 92% rename from lib/src/utils/screenshot.dart rename to lib/src/visual/helpers/screenshot.helper.dart index 1bf4517b..02c906d9 100644 --- a/lib/src/utils/screenshot.dart +++ b/lib/src/visual/helpers/screenshot.helper.dart @@ -7,8 +7,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:twonly/src/utils/log.dart'; -class ScreenshotImage { - ScreenshotImage({ +class ScreenshotImageHelper { + ScreenshotImageHelper({ this.image, this.imageBytes, this.imageBytesFuture, @@ -46,7 +46,7 @@ class ScreenshotController { } late GlobalKey _containerKey; - Future capture({double? pixelRatio}) async { + Future capture({double? pixelRatio}) async { try { final findRenderObject = _containerKey.currentContext?.findRenderObject(); if (findRenderObject == null) { @@ -62,7 +62,7 @@ class ScreenshotController { } } final image = await boundary.toImage(pixelRatio: tmpPixelRatio ?? 1); - return ScreenshotImage(image: image); + return ScreenshotImageHelper(image: image); } catch (e) { Log.error(e); } diff --git a/lib/src/views/components/video_player_wrapper.dart b/lib/src/visual/helpers/video_player.helper.dart similarity index 84% rename from lib/src/views/components/video_player_wrapper.dart rename to lib/src/visual/helpers/video_player.helper.dart index be470fc9..5df1cd14 100644 --- a/lib/src/views/components/video_player_wrapper.dart +++ b/lib/src/visual/helpers/video_player.helper.dart @@ -4,18 +4,18 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; -class VideoPlayerWrapper extends StatefulWidget { - const VideoPlayerWrapper({ +class VideoPlayerHelper extends StatefulWidget { + const VideoPlayerHelper({ required this.videoPath, super.key, }); final File videoPath; @override - State createState() => _VideoPlayerWrapperState(); + State createState() => _VideoPlayerHelperState(); } -class _VideoPlayerWrapperState extends State { +class _VideoPlayerHelperState extends State { late VideoPlayerController _controller; @override diff --git a/lib/src/views/components/loader/ripple.loader.dart b/lib/src/visual/loader/ripple.loader.dart similarity index 100% rename from lib/src/views/components/loader/ripple.loader.dart rename to lib/src/visual/loader/ripple.loader.dart diff --git a/lib/src/views/components/loader/three_rotating_dots.loader.dart b/lib/src/visual/loader/three_rotating_dots.loader.dart similarity index 100% rename from lib/src/views/components/loader/three_rotating_dots.loader.dart rename to lib/src/visual/loader/three_rotating_dots.loader.dart diff --git a/lib/src/visual/themes/colors.dart b/lib/src/visual/themes/colors.dart new file mode 100644 index 00000000..c3d88865 --- /dev/null +++ b/lib/src/visual/themes/colors.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/src/database/tables/mediafiles.table.dart'; +import 'package:twonly/src/database/tables/messages.table.dart'; +import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/utils/misc.dart'; + +class DefaultColors { + static const messageSelf = Color.fromARGB(255, 58, 136, 102); + static const messageOther = Color.fromARGB(233, 68, 137, 255); +} + +Color getMessageColorFromType( + Message message, + MediaFile? mediaFile, + BuildContext context, +) { + Color color; + + if (message.type == MessageType.restoreFlameCounter.name) { + color = Colors.orange; + } else if (message.type == MessageType.text.name) { + color = Colors.blueAccent; + } else if (mediaFile != null) { + if (mediaFile.requiresAuthentication) { + color = context.color.primary; + } else { + if (mediaFile.type == MediaType.video) { + color = const Color.fromARGB(255, 243, 33, 208); + } else if (mediaFile.type == MediaType.audio) { + color = const Color.fromARGB(255, 252, 149, 85); + } else { + color = Colors.redAccent; + } + } + } else { + return (isDarkMode(context)) ? Colors.white : Colors.black; + } + return color; +} diff --git a/lib/src/themes/dark.dart b/lib/src/visual/themes/dark.dart similarity index 100% rename from lib/src/themes/dark.dart rename to lib/src/visual/themes/dark.dart diff --git a/lib/src/themes/light.dart b/lib/src/visual/themes/light.dart similarity index 100% rename from lib/src/themes/light.dart rename to lib/src/visual/themes/light.dart diff --git a/lib/src/views/camera/camera_preview_components/camera_preview.dart b/lib/src/visual/views/camera/camera_preview_components/camera_preview.dart similarity index 93% rename from lib/src/views/camera/camera_preview_components/camera_preview.dart rename to lib/src/visual/views/camera/camera_preview_components/camera_preview.dart index 648e34bc..823798fd 100644 --- a/lib/src/views/camera/camera_preview_components/camera_preview.dart +++ b/lib/src/visual/views/camera/camera_preview_components/camera_preview.dart @@ -1,8 +1,8 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; -import 'package:twonly/src/utils/screenshot.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; -import 'package:twonly/src/views/components/media_view_sizing.dart'; +import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart'; +import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart'; class MainCameraPreview extends StatelessWidget { const MainCameraPreview({ @@ -19,7 +19,7 @@ class MainCameraPreview extends StatelessWidget { return Container(); } return Positioned.fill( - child: MediaViewSizing( + child: MediaViewSizingHelper( requiredHeight: 0, additionalPadding: 59, bottomNavigation: Container(), diff --git a/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart similarity index 95% rename from lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart rename to lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart index 94708969..9bf92347 100644 --- a/lib/src/views/camera/camera_preview_components/camera_preview_controller_view.dart +++ b/lib/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart @@ -13,27 +13,28 @@ import 'package:image_picker/image_picker.dart'; import 'package:lottie/lottie.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.dart'; -import 'package:twonly/src/utils/screenshot.dart'; -import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/permissions_view.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/send_to.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/video_recording_time.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/zoom_selector.dart'; -import 'package:twonly/src/views/camera/share_image_editor.view.dart'; -import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; -import 'package:twonly/src/views/components/avatar_icon.component.dart'; -import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart'; -import 'package:twonly/src/views/components/media_view_sizing.dart'; -import 'package:twonly/src/views/home.view.dart'; +import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; +import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart'; +import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; +import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/permissions_view.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/send_to.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/video_recording_time.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/zoom_selector.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor.view.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart'; +import 'package:twonly/src/visual/views/home.view.dart'; import 'package:url_launcher/url_launcher_string.dart'; int maxVideoRecordingTime = 60; @@ -209,7 +210,7 @@ class _CameraPreviewViewState extends State { _hasAudioPermission = await Permission.microphone.isGranted; if (!_hasAudioPermission && - !AppSession.currentUser.requestedAudioPermission) { + !appSession.currentUser.requestedAudioPermission) { await updateUser((u) { u.requestedAudioPermission = true; }); @@ -311,7 +312,7 @@ class _CameraPreviewViewState extends State { } Future pushMediaEditor( - ScreenshotImage? screenshotImage, + ScreenshotImageHelper? screenshotImage, File? videoFilePath, { bool sharedFromGallery = false, MediaType? mediaType, @@ -321,7 +322,7 @@ class _CameraPreviewViewState extends State { ((videoFilePath != null) ? MediaType.video : MediaType.image); final mediaFileService = await initializeMediaUpload( type, - AppSession.currentUser.defaultShowTime, + appSession.currentUser.defaultShowTime, isDraftMedia: true, ); if (!mounted) return true; @@ -439,7 +440,7 @@ class _CameraPreviewViewState extends State { Log.info('Picket from gallery: ${pickedFile.path}'); File? videoFilePath; - ScreenshotImage? image; + ScreenshotImageHelper? image; MediaType? mediaType; final isImage = imageExtensions.any( @@ -449,7 +450,9 @@ class _CameraPreviewViewState extends State { if (pickedFile.name.contains('.gif')) { mediaType = MediaType.gif; } - image = ScreenshotImage(imageBytesFuture: pickedFile.readAsBytes()); + image = ScreenshotImageHelper( + imageBytesFuture: pickedFile.readAsBytes(), + ); } else { videoFilePath = File(pickedFile.path); } @@ -596,7 +599,7 @@ class _CameraPreviewViewState extends State { mc.cameraController == null) { return Container(); } - return MediaViewSizing( + return MediaViewSizingHelper( requiredHeight: 0, additionalPadding: 59, bottomNavigation: Container(), diff --git a/lib/src/views/camera/camera_preview_components/face_filters.dart b/lib/src/visual/views/camera/camera_preview_components/face_filters.dart similarity index 76% rename from lib/src/views/camera/camera_preview_components/face_filters.dart rename to lib/src/visual/views/camera/camera_preview_components/face_filters.dart index 11c5e197..c555742a 100644 --- a/lib/src/views/camera/camera_preview_components/face_filters.dart +++ b/lib/src/visual/views/camera/camera_preview_components/face_filters.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart'; enum FaceFilterType { none, diff --git a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart b/lib/src/visual/views/camera/camera_preview_components/main_camera_controller.dart similarity index 94% rename from lib/src/views/camera/camera_preview_components/main_camera_controller.dart rename to lib/src/visual/views/camera/camera_preview_components/main_camera_controller.dart index df4cf309..25e40b80 100644 --- a/lib/src/views/camera/camera_preview_components/main_camera_controller.dart +++ b/lib/src/visual/views/camera/camera_preview_components/main_camera_controller.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; + import 'package:camera/camera.dart'; import 'package:clock/clock.dart'; import 'package:collection/collection.dart'; @@ -11,6 +12,7 @@ import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/qr.pb.dart'; @@ -18,13 +20,13 @@ import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/qr.dart'; -import 'package:twonly/src/utils/screenshot.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/barcode_detector_painter.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; +import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/barcode_detector_painter.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; class ScannedVerifiedContact { ScannedVerifiedContact({required this.contact, required this.verificationOk}); @@ -135,7 +137,7 @@ class MainCameraController { await cameraController?.initialize(); await cameraController?.startImageStream(_processCameraImage); await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor); - if (AppSession.currentUser.videoStabilizationEnabled && !kDebugMode) { + if (appSession.currentUser.videoStabilizationEnabled && !kDebugMode) { await cameraController?.setVideoStabilizationMode( VideoStabilizationMode.level1, ); @@ -395,7 +397,7 @@ class MainCameraController { } } } else { - if (profile.username != AppSession.currentUser.username) { + if (profile.username != appSession.currentUser.username) { if (scannedNewProfiles[profile.userId.toInt()] == null) { await HapticFeedback.heavyImpact(); scannedNewProfiles[profile.userId.toInt()] = ScannedNewProfile( diff --git a/lib/src/views/camera/camera_preview_components/painters/barcode_detector_painter.dart b/lib/src/visual/views/camera/camera_preview_components/painters/barcode_detector_painter.dart similarity index 100% rename from lib/src/views/camera/camera_preview_components/painters/barcode_detector_painter.dart rename to lib/src/visual/views/camera/camera_preview_components/painters/barcode_detector_painter.dart diff --git a/lib/src/views/camera/camera_preview_components/painters/coordinates_translator.dart b/lib/src/visual/views/camera/camera_preview_components/painters/coordinates_translator.dart similarity index 100% rename from lib/src/views/camera/camera_preview_components/painters/coordinates_translator.dart rename to lib/src/visual/views/camera/camera_preview_components/painters/coordinates_translator.dart diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart b/lib/src/visual/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart similarity index 93% rename from lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart rename to lib/src/visual/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart index 2380273e..4804ac75 100644 --- a/lib/src/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart +++ b/lib/src/visual/views/camera/camera_preview_components/painters/face_filters/beard_filter_painter.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/face_filters.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/coordinates_translator.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/face_filters.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/coordinates_translator.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; class BeardFilterPainter extends FaceFilterPainter { BeardFilterPainter( diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart b/lib/src/visual/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart similarity index 96% rename from lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart rename to lib/src/visual/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart index 5a6492f3..78a6cdd6 100644 --- a/lib/src/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart +++ b/lib/src/visual/views/camera/camera_preview_components/painters/face_filters/dog_filter_painter.dart @@ -6,8 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/coordinates_translator.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/coordinates_translator.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart'; class DogFilterPainter extends FaceFilterPainter { DogFilterPainter( diff --git a/lib/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart b/lib/src/visual/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart similarity index 100% rename from lib/src/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart rename to lib/src/visual/views/camera/camera_preview_components/painters/face_filters/face_filter_painter.dart diff --git a/lib/src/views/camera/camera_preview_components/permissions_view.dart b/lib/src/visual/views/camera/camera_preview_components/permissions_view.dart similarity index 100% rename from lib/src/views/camera/camera_preview_components/permissions_view.dart rename to lib/src/visual/views/camera/camera_preview_components/permissions_view.dart diff --git a/lib/src/views/camera/camera_preview_components/save_to_gallery.dart b/lib/src/visual/views/camera/camera_preview_components/save_to_gallery.dart similarity index 95% rename from lib/src/views/camera/camera_preview_components/save_to_gallery.dart rename to lib/src/visual/views/camera/camera_preview_components/save_to_gallery.dart index efb57d43..8b70c224 100644 --- a/lib/src/views/camera/camera_preview_components/save_to_gallery.dart +++ b/lib/src/visual/views/camera/camera_preview_components/save_to_gallery.dart @@ -1,13 +1,14 @@ import 'dart:async'; + import 'package:clock/clock.dart'; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/screenshot.dart'; +import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; class SaveToGalleryButton extends StatefulWidget { const SaveToGalleryButton({ @@ -17,7 +18,7 @@ class SaveToGalleryButton extends StatefulWidget { this.storeImageAsOriginal, super.key, }); - final Future Function()? storeImageAsOriginal; + final Future Function()? storeImageAsOriginal; final bool displayButtonLabel; final MediaFileService mediaService; final bool isLoading; diff --git a/lib/src/views/camera/camera_preview_components/send_to.dart b/lib/src/visual/views/camera/camera_preview_components/send_to.dart similarity index 100% rename from lib/src/views/camera/camera_preview_components/send_to.dart rename to lib/src/visual/views/camera/camera_preview_components/send_to.dart diff --git a/lib/src/views/camera/camera_preview_components/video_recording_time.dart b/lib/src/visual/views/camera/camera_preview_components/video_recording_time.dart similarity index 100% rename from lib/src/views/camera/camera_preview_components/video_recording_time.dart rename to lib/src/visual/views/camera/camera_preview_components/video_recording_time.dart diff --git a/lib/src/views/camera/camera_preview_components/zoom_selector.dart b/lib/src/visual/views/camera/camera_preview_components/zoom_selector.dart similarity index 98% rename from lib/src/views/camera/camera_preview_components/zoom_selector.dart rename to lib/src/visual/views/camera/camera_preview_components/zoom_selector.dart index 07450e07..b0a22bf5 100644 --- a/lib/src/views/camera/camera_preview_components/zoom_selector.dart +++ b/lib/src/visual/views/camera/camera_preview_components/zoom_selector.dart @@ -6,7 +6,7 @@ import 'dart:math'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:twonly/globals.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; class CameraZoomButtons extends StatefulWidget { const CameraZoomButtons({ diff --git a/lib/src/views/camera/camera_qr_scanner.view.dart b/lib/src/visual/views/camera/camera_qr_scanner.view.dart similarity index 80% rename from lib/src/views/camera/camera_qr_scanner.view.dart rename to lib/src/visual/views/camera/camera_qr_scanner.view.dart index 6d6201bf..1ef5d9e6 100644 --- a/lib/src/views/camera/camera_qr_scanner.view.dart +++ b/lib/src/visual/views/camera/camera_qr_scanner.view.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart'; class QrCodeScannerView extends StatefulWidget { const QrCodeScannerView({super.key}); diff --git a/lib/src/views/camera/camera_send_to.view.dart b/lib/src/visual/views/camera/camera_send_to.view.dart similarity index 81% rename from lib/src/views/camera/camera_send_to.view.dart rename to lib/src/visual/views/camera/camera_send_to.view.dart index cdda1331..0f53fe8b 100644 --- a/lib/src/views/camera/camera_send_to.view.dart +++ b/lib/src/visual/views/camera/camera_send_to.view.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:twonly/src/database/twonly.db.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart'; class CameraSendToView extends StatefulWidget { const CameraSendToView(this.sendToGroup, {super.key}); diff --git a/lib/src/views/camera/share_image_contact_selection.view.dart b/lib/src/visual/views/camera/share_image_contact_selection.view.dart similarity index 93% rename from lib/src/views/camera/share_image_contact_selection.view.dart rename to lib/src/visual/views/camera/share_image_contact_selection.view.dart index 78e2ca73..f30beef7 100644 --- a/lib/src/views/camera/share_image_contact_selection.view.dart +++ b/lib/src/visual/views/camera/share_image_contact_selection.view.dart @@ -1,22 +1,24 @@ import 'dart:async'; import 'dart:collection'; + import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/flame.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/screenshot.dart'; -import 'package:twonly/src/views/camera/share_image_contact_selection/best_friends_selector.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/background.layer.dart'; -import 'package:twonly/src/views/components/avatar_icon.component.dart'; -import 'package:twonly/src/views/components/flame.dart'; -import 'package:twonly/src/views/components/headline.dart'; +import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; +import 'package:twonly/src/visual/components/flame_counter.comp.dart'; +import 'package:twonly/src/visual/decorations/input_text.decoration.dart'; +import 'package:twonly/src/visual/elements/headline.element.dart'; +import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; +import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/best_friends_selector.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/background.layer.dart'; class ShareImageView extends StatefulWidget { const ShareImageView({ @@ -29,7 +31,7 @@ class ShareImageView extends StatefulWidget { }); final HashSet selectedGroupIds; final void Function(String, bool) updateSelectedGroupIds; - final Future? mediaStoreFuture; + final Future? mediaStoreFuture; final MediaFileService mediaFileService; final AdditionalMessageData? additionalData; @@ -45,7 +47,7 @@ class _ShareImageView extends State { bool sendingImage = false; bool mediaStoreFutureReady = false; - ScreenshotImage? _screenshotImage; + ScreenshotImageHelper? _screenshotImage; bool hideArchivedUsers = true; final TextEditingController searchUserName = TextEditingController(); late StreamSubscription> allGroupSub; @@ -199,7 +201,7 @@ class _ShareImageView extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - HeadLineComponent(context.lang.shareImageAllUsers), + HeadLineComp(context.lang.shareImageAllUsers), if (contacts.any((x) => x.archived)) Row( children: [ @@ -254,7 +256,7 @@ class _ShareImageView extends State { children: [ if (widget.mediaFileService.mediaFile.type == MediaType.image && _screenshotImage?.image != null && - AppSession.currentUser.showShowImagePreviewWhenSending) + appSession.currentUser.showShowImagePreviewWhenSending) SizedBox( height: 100, width: 100 * 9 / 16, diff --git a/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart b/lib/src/visual/views/camera/share_image_contact_selection_components/best_friends_selector.dart similarity index 95% rename from lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart rename to lib/src/visual/views/camera/share_image_contact_selection_components/best_friends_selector.dart index a15b23eb..78c87516 100644 --- a/lib/src/views/camera/share_image_contact_selection/best_friends_selector.dart +++ b/lib/src/visual/views/camera/share_image_contact_selection_components/best_friends_selector.dart @@ -1,11 +1,12 @@ import 'dart:collection'; + import 'package:flutter/material.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/components/avatar_icon.component.dart'; -import 'package:twonly/src/views/components/flame.dart'; -import 'package:twonly/src/views/components/headline.dart'; +import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; +import 'package:twonly/src/visual/components/flame_counter.comp.dart'; +import 'package:twonly/src/visual/elements/headline.element.dart'; class BestFriendsSelector extends StatelessWidget { const BestFriendsSelector({ @@ -32,7 +33,7 @@ class BestFriendsSelector extends StatelessWidget { Row( children: [ Expanded( - child: HeadLineComponent(title), + child: HeadLineComp(title), ), if (showSelectAll) GestureDetector( diff --git a/lib/src/views/camera/share_image_contact_selection/select_show_time.dart b/lib/src/visual/views/camera/share_image_contact_selection_components/select_show_time.dart similarity index 100% rename from lib/src/views/camera/share_image_contact_selection/select_show_time.dart rename to lib/src/visual/views/camera/share_image_contact_selection_components/select_show_time.dart diff --git a/lib/src/views/camera/share_image_editor.view.dart b/lib/src/visual/views/camera/share_image_editor.view.dart similarity index 93% rename from lib/src/views/camera/share_image_editor.view.dart rename to lib/src/visual/views/camera/share_image_editor.view.dart index f7ce1c9a..987e1498 100644 --- a/lib/src/views/camera/share_image_editor.view.dart +++ b/lib/src/visual/views/camera/share_image_editor.view.dart @@ -6,28 +6,28 @@ import 'dart:collection'; import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/database/tables/mediafiles.table.dart'; import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart'; -import 'package:twonly/src/services/api/mediafiles/upload.service.dart'; +import 'package:twonly/src/services/api/mediafiles/upload.api.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; +import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/utils/screenshot.dart'; -import 'package:twonly/src/utils/storage.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; -import 'package:twonly/src/views/camera/camera_preview_components/save_to_gallery.dart'; -import 'package:twonly/src/views/camera/share_image_contact_selection.view.dart'; -import 'package:twonly/src/views/camera/share_image_contact_selection/select_show_time.dart'; -import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/share_image_editor/image_item.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers_viewer.dart'; -import 'package:twonly/src/views/components/emoji_picker.bottom.dart'; -import 'package:twonly/src/views/components/media_view_sizing.dart'; -import 'package:twonly/src/views/components/notification_badge.dart'; +import 'package:twonly/src/visual/components/emoji_picker.bottom.dart'; +import 'package:twonly/src/visual/components/notification_badge.comp.dart'; +import 'package:twonly/src/visual/helpers/media_view_sizing.helper.dart'; +import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart'; +import 'package:twonly/src/visual/views/camera/camera_preview_components/save_to_gallery.dart'; +import 'package:twonly/src/visual/views/camera/share_image_contact_selection.view.dart'; +import 'package:twonly/src/visual/views/camera/share_image_contact_selection_components/select_show_time.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/image_item.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers_viewer.dart'; import 'package:video_player/video_player.dart'; List layers = []; @@ -44,7 +44,7 @@ class ShareImageEditorView extends StatefulWidget { this.sendToGroup, this.mainCameraController, }); - final ScreenshotImage? screenshotImage; + final ScreenshotImageHelper? screenshotImage; final Group? sendToGroup; final bool sharedFromGallery; final MediaFileService mediaFileService; @@ -95,10 +95,12 @@ class _ShareImageEditorView extends State { loadImage(widget.screenshotImage!); } else { if (widget.mediaFileService.tempPath.existsSync()) { - loadImage(ScreenshotImage(file: widget.mediaFileService.tempPath)); + loadImage( + ScreenshotImageHelper(file: widget.mediaFileService.tempPath), + ); } else if (widget.mediaFileService.originalPath.existsSync()) { loadImage( - ScreenshotImage(file: widget.mediaFileService.originalPath), + ScreenshotImageHelper(file: widget.mediaFileService.originalPath), ); } } @@ -270,7 +272,7 @@ class _ShareImageEditorView extends State { }, ), const SizedBox(height: 8), - NotificationBadge( + NotificationBadgeComp( count: (media.type == MediaType.video) ? '0' : media.displayLimitInMilliseconds == null @@ -466,7 +468,7 @@ class _ShareImageEditorView extends State { } } - Future getEditedImageBytes() async { + Future getEditedImageBytes() async { if (layers.length == 1) { if (layers.first is BackgroundLayerData) { return (layers.first as BackgroundLayerData).image.image; @@ -498,7 +500,7 @@ class _ShareImageEditorView extends State { return image; } - Future storeImageAsOriginal() async { + Future storeImageAsOriginal() async { if (mediaService.overlayImagePath.existsSync()) { mediaService.overlayImagePath.deleteSync(); } @@ -510,7 +512,7 @@ class _ShareImageEditorView extends State { mediaService.originalPath.deleteSync(); } } - ScreenshotImage? image; + ScreenshotImageHelper? image; var bytes = await widget.screenshotImage?.getBytes(); if (media.type == MediaType.gif) { if (bytes != null) { @@ -535,12 +537,14 @@ class _ShareImageEditorView extends State { return image; } - Future storeIoImageAsDraft(ScreenshotImage screenshotImage) async { + Future storeIoImageAsDraft( + ScreenshotImageHelper screenshotImage, + ) async { final imageBytes = await screenshotImage.getBytes(); mediaService.originalPath.writeAsBytesSync(imageBytes!.toList()); } - Future loadImage(ScreenshotImage screenshotImage) async { + Future loadImage(ScreenshotImageHelper screenshotImage) async { if (screenshotImage.image == null && screenshotImage.imageBytes == null && screenshotImage.imageBytesFuture != null) { @@ -684,7 +688,7 @@ class _ShareImageEditorView extends State { ); setState(() {}); }, - child: MediaViewSizing( + child: MediaViewSizingHelper( requiredHeight: 59, bottomNavigation: ColoredBox( color: Theme.of(context).colorScheme.surface, diff --git a/lib/src/views/camera/share_image_editor/action_button.dart b/lib/src/visual/views/camera/share_image_editor_components/action_button.dart similarity index 100% rename from lib/src/views/camera/share_image_editor/action_button.dart rename to lib/src/visual/views/camera/share_image_editor_components/action_button.dart diff --git a/lib/src/views/camera/share_image_editor/image_item.dart b/lib/src/visual/views/camera/share_image_editor_components/image_item.dart similarity index 65% rename from lib/src/views/camera/share_image_editor/image_item.dart rename to lib/src/visual/views/camera/share_image_editor_components/image_item.dart index c2511d57..6bf97f97 100755 --- a/lib/src/views/camera/share_image_editor/image_item.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/image_item.dart @@ -1,14 +1,14 @@ import 'dart:async'; -import 'package:twonly/src/utils/screenshot.dart'; +import 'package:twonly/src/visual/helpers/screenshot.helper.dart'; class ImageItem { ImageItem(); int width = 1; int height = 1; - ScreenshotImage? image; + ScreenshotImageHelper? image; Completer loader = Completer(); - void load(ScreenshotImage img) { + void load(ScreenshotImageHelper img) { image = img; if (image?.image != null) { height = image!.image!.height; diff --git a/lib/src/views/camera/share_image_editor/layer_data.dart b/lib/src/visual/views/camera/share_image_editor_components/layer_data.dart similarity index 90% rename from lib/src/views/camera/share_image_editor/layer_data.dart rename to lib/src/visual/views/camera/share_image_editor_components/layer_data.dart index ffee88ad..96153d07 100755 --- a/lib/src/views/camera/share_image_editor/layer_data.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layer_data.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hand_signature/signature.dart'; -import 'package:twonly/src/views/camera/share_image_editor/image_item.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/image_item.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; /// Layer class with some common properties class Layer { diff --git a/lib/src/views/camera/share_image_editor/layers/background.layer.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/background.layer.dart similarity index 93% rename from lib/src/views/camera/share_image_editor/layers/background.layer.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/background.layer.dart index 96213423..91670ad2 100755 --- a/lib/src/views/camera/share_image_editor/layers/background.layer.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/background.layer.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:photo_view/photo_view.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart'; class BackgroundLayer extends StatefulWidget { const BackgroundLayer({ diff --git a/lib/src/views/camera/share_image_editor/layers/draw.layer.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/draw.layer.dart similarity index 95% rename from lib/src/views/camera/share_image_editor/layers/draw.layer.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/draw.layer.dart index aabddfd2..6f897ff8 100644 --- a/lib/src/views/camera/share_image_editor/layers/draw.layer.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/draw.layer.dart @@ -3,9 +3,9 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:hand_signature/signature.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/draw/custom_hand_signature.dart'; class DrawLayer extends StatefulWidget { const DrawLayer({ diff --git a/lib/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/draw/custom_hand_signature.dart similarity index 100% rename from lib/src/views/camera/share_image_editor/layers/draw/custom_hand_signature.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/draw/custom_hand_signature.dart diff --git a/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/emoji.layer.dart similarity index 97% rename from lib/src/views/camera/share_image_editor/layers/emoji.layer.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/emoji.layer.dart index d01afc2a..2724251a 100755 --- a/lib/src/views/camera/share_image_editor/layers/emoji.layer.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/emoji.layer.dart @@ -5,8 +5,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/views/camera/share_image_editor/action_button.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/action_button.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart'; /// Emoji layer class EmojiLayer extends StatefulWidget { diff --git a/lib/src/views/camera/share_image_editor/layers/filter.layer.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart similarity index 87% rename from lib/src/views/camera/share_image_editor/layers/filter.layer.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart index 9b9e0154..954d34c3 100644 --- a/lib/src/views/camera/share_image_editor/layers/filter.layer.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/filters/image_filter.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/filters/location_filter.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/datetime_filter.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/image_filter.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/location_filter.dart'; /// Main layer class FilterLayer extends StatefulWidget { diff --git a/lib/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/datetime_filter.dart similarity index 88% rename from lib/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/filters/datetime_filter.dart index 78bae80f..72f5564d 100644 --- a/lib/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/datetime_filter.dart @@ -1,7 +1,7 @@ import 'package:clock/clock.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart'; class DateTimeFilter extends StatelessWidget { const DateTimeFilter({super.key, this.color = Colors.white}); diff --git a/lib/src/views/camera/share_image_editor/layers/filters/image_filter.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/image_filter.dart similarity index 84% rename from lib/src/views/camera/share_image_editor/layers/filters/image_filter.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/filters/image_filter.dart index 920a9e47..8c6bfdcd 100644 --- a/lib/src/views/camera/share_image_editor/layers/filters/image_filter.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/image_filter.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart'; class ImageFilter extends StatelessWidget { const ImageFilter({required this.imagePath, super.key}); diff --git a/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/location_filter.dart similarity index 94% rename from lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/filters/location_filter.dart index e3979327..55021aa8 100644 --- a/lib/src/views/camera/share_image_editor/layers/filters/location_filter.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/filters/location_filter.dart @@ -8,11 +8,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; -import 'package:twonly/globals.dart'; +import 'package:twonly/locator.dart'; import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/filter.layer.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/filters/datetime_filter.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filter.layer.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/filters/datetime_filter.dart'; class LocationFilter extends StatefulWidget { const LocationFilter({super.key}); diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview.layer.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview.layer.dart similarity index 63% rename from lib/src/views/camera/share_image_editor/layers/link_preview.layer.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview.layer.dart index 4de15b01..7976feb7 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview.layer.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview.layer.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layer_data.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; -import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart'; +import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layer_data.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/custom.card.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/mastodon.card.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/twitter.card.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/youtube.card.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parse_link.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; class LinkPreviewLayer extends StatefulWidget { const LinkPreviewLayer({ diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/custom.card.dart similarity index 96% rename from lib/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/custom.card.dart index f7d7e230..bf30b80e 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/custom.card.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/custom.card.dart @@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; class CustomLinkCard extends StatelessWidget { const CustomLinkCard({required this.info, super.key}); diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/mastodon.card.dart similarity index 95% rename from lib/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/mastodon.card.dart index f2fe80a8..40041bdb 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/mastodon.card.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/mastodon.card.dart @@ -2,8 +2,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; -import 'package:twonly/src/views/components/loader/three_rotating_dots.loader.dart'; +import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; class MastodonPostCard extends StatelessWidget { const MastodonPostCard({required this.info, super.key}); diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/twitter.card.dart similarity index 96% rename from lib/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/twitter.card.dart index a822c595..0a076d64 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/twitter.card.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/twitter.card.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; // Assuming the same Metadata import structure -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; class TwitterPostCard extends StatelessWidget { const TwitterPostCard({required this.info, super.key}); diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/youtube.card.dart similarity index 96% rename from lib/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/youtube.card.dart index c9bbb855..4c253125 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/cards/youtube.card.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/cards/youtube.card.dart @@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/src/database/daos/contacts.dao.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; class YouTubePostCard extends StatelessWidget { const YouTubePostCard({required this.info, super.key}); diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parse_link.dart similarity index 72% rename from lib/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parse_link.dart index 0923f5ca..a0df2ec4 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/parse_link.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parse_link.dart @@ -7,16 +7,16 @@ import 'package:html/dom.dart' show Document; import 'package:html/parser.dart'; import 'package:http/http.dart' as http; import 'package:twonly/src/utils/log.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/html.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/mastodon.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/og.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/other.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/twitter.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/youtube.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/utils.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/html.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/json_ld.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/mastodon.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/og.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/other.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/twitter.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/util.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/youtube.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/utils.dart'; Future getMetadata(String link) async { const userAgent = 'WhatsApp/2.21.12.21 A'; diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart similarity index 100% rename from lib/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/html.parser.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/html.parser.dart similarity index 100% rename from lib/src/views/camera/share_image_editor/layers/link_preview/parser/html.parser.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/html.parser.dart diff --git a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/json_ld.parser.dart similarity index 88% rename from lib/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart rename to lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/json_ld.parser.dart index beb8ef3e..6ca5f7fe 100644 --- a/lib/src/views/camera/share_image_editor/layers/link_preview/parser/json_ld.parser.dart +++ b/lib/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/json_ld.parser.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:html/dom.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/base.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/og.parser.dart'; -import 'package:twonly/src/views/camera/share_image_editor/layers/link_preview/parser/util.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/base.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/og.parser.dart'; +import 'package:twonly/src/visual/views/camera/share_image_editor_components/layers/link_preview/parser/util.dart'; /// Parses [Metadata] from `json-ld` data in `