refactor, update and rotation fix

This commit is contained in:
otsmr 2025-04-21 21:32:18 +02:00
parent 8f29461f72
commit f5b4e35e18
106 changed files with 2262 additions and 551 deletions

View file

@ -63,5 +63,5 @@ flutter {
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
}

View file

@ -11,6 +11,7 @@
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user

View file

@ -4,7 +4,7 @@
SRC_DIR="../twonly-server/twonly/src/"
DST_DIR="$(pwd)/lib/src/proto/"
DST_DIR="$(pwd)/lib/src/model/protobuf/"
mkdir $DST_DIR

View file

@ -5,54 +5,54 @@ PODS:
- Flutter
- cryptography_flutter_plus (0.2.0):
- Flutter
- Firebase (11.8.0):
- Firebase/Core (= 11.8.0)
- Firebase/Core (11.8.0):
- Firebase (11.10.0):
- Firebase/Core (= 11.10.0)
- Firebase/Core (11.10.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 11.8.0)
- Firebase/CoreOnly (11.8.0):
- FirebaseCore (~> 11.8.0)
- Firebase/Messaging (11.8.0):
- FirebaseAnalytics (~> 11.10.0)
- Firebase/CoreOnly (11.10.0):
- FirebaseCore (~> 11.10.0)
- Firebase/Messaging (11.10.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.8.0)
- firebase_core (3.12.1):
- Firebase/CoreOnly (= 11.8.0)
- FirebaseMessaging (~> 11.10.0)
- firebase_core (3.13.0):
- Firebase/CoreOnly (= 11.10.0)
- Flutter
- firebase_messaging (15.2.4):
- Firebase/Messaging (= 11.8.0)
- firebase_messaging (15.2.5):
- Firebase/Messaging (= 11.10.0)
- firebase_core
- Flutter
- FirebaseAnalytics (11.8.0):
- FirebaseAnalytics/AdIdSupport (= 11.8.0)
- FirebaseCore (~> 11.8.0)
- FirebaseAnalytics (11.10.0):
- FirebaseAnalytics/AdIdSupport (= 11.10.0)
- FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseAnalytics/AdIdSupport (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseAnalytics/AdIdSupport (11.10.0):
- FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0)
- GoogleAppMeasurement (= 11.8.0)
- GoogleAppMeasurement (= 11.10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- FirebaseCore (11.8.1):
- FirebaseCoreInternal (~> 11.8.0)
- FirebaseCore (11.10.0):
- FirebaseCoreInternal (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.8.0):
- FirebaseCoreInternal (11.10.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseInstallations (11.10.0):
- FirebaseCore (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.8.0):
- FirebaseCore (~> 11.8.0)
- FirebaseMessaging (11.10.0):
- FirebaseCore (~> 11.10.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@ -78,21 +78,21 @@ PODS:
- gal (1.0.0):
- Flutter
- FlutterMacOS
- GoogleAppMeasurement (11.8.0):
- GoogleAppMeasurement/AdIdSupport (= 11.8.0)
- GoogleAppMeasurement (11.10.0):
- GoogleAppMeasurement/AdIdSupport (= 11.10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/AdIdSupport (11.8.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.8.0)
- GoogleAppMeasurement/AdIdSupport (11.10.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 11.10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- nanopb (~> 3.30910.0)
- GoogleAppMeasurement/WithoutAdIdSupport (11.8.0):
- GoogleAppMeasurement/WithoutAdIdSupport (11.10.0):
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/MethodSwizzler (~> 8.0)
- GoogleUtilities/Network (~> 8.0)
@ -317,25 +317,25 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf
camera_avfoundation: be3be85408cd4126f250386828e9b1dfa40ab436
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
cryptography_flutter_plus: 44f4e9e4079395fcbb3e7809c0ac2c6ae2d9576f
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_core: 8d552814f6c01ccde5d88939fced4ec26f2f5510
firebase_messaging: 8b96a4f09841c15a16b96973ef5c3dcfc1a064e4
FirebaseAnalytics: 4fd42def128146e24e480e89f310e3d8534ea42b
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
firebase_core: 2d4534e7b489907dcede540c835b48981d890943
firebase_messaging: 75bc93a4df25faccad67f6662ae872ac9ae69b64
FirebaseAnalytics: 4e42333f02cf78ed93703a5c36f36dd518aebdef
FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3
FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468
flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb
gal: baecd024ebfd13c441269ca7404792a7152fde89
GoogleAppMeasurement: fc0817122bd4d4189164f85374e06773b9561896
GoogleAppMeasurement: 36684bfb3ee034e2b42b4321eb19da3a1b81e65d
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a

View file

@ -24,6 +24,10 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>FIREBASE_ANALYTICS_COLLECTION_ENABLED</key>
<false/>
<key>FirebaseAutomaticScreenReportingEnabled</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
@ -45,20 +49,9 @@
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>FIREBASE_ANALYTICS_COLLECTION_ENABLED</key><false/>
<key>FirebaseAutomaticScreenReportingEnabled</key> <false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -1,4 +1,6 @@
synthetic-package: false
arb-dir: lib/src/localization
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
untranslated-messages-file: build/l10n.log
output-dir: lib/src/localization/generated

View file

@ -1,5 +1,6 @@
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/localization/generated/app_localizations.dart';
import 'package:twonly/src/providers/connection_provider.dart';
import 'package:twonly/src/providers/settings_change_provider.dart';
import 'package:twonly/src/services/notification_service.dart';
@ -8,13 +9,12 @@ import 'package:twonly/src/views/onboarding/onboarding_view.dart';
import 'package:twonly/src/views/home_view.dart';
import 'package:twonly/src/views/onboarding/register_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'dart:async';
// these global function can be called from anywhere to update
// the ui when something changed. The callbacks will be set by
// MyApp widget.
// App widget.
// this callback is called by the apiProvider
Function(bool) globalCallbackConnectionState = (a) {};
@ -23,14 +23,13 @@ bool globalIsAppInBackground = true;
// these two callbacks are called on updated to the corresponding database
/// The Widget that configures your application.
class MyApp extends StatefulWidget {
const MyApp({super.key});
class App extends StatefulWidget {
const App({super.key});
@override
State<MyApp> createState() => _MyAppState();
State<App> createState() => _AppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
class _AppState extends State<App> with WidgetsBindingObserver {
bool wasPaused = false;
@override
@ -60,7 +59,6 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
globalIsAppInBackground = false;
twonlyDatabase.markUpdated();
apiProvider.connect();
// _stopService();
}
} else if (state == AppLifecycleState.paused) {
wasPaused = true;
@ -70,20 +68,13 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void dispose() {
// apiProvider.close(() {});
WidgetsBinding.instance.removeObserver(this);
// disable globalCallbacks to the flutter tree
globalCallbackConnectionState = (a) {};
super.dispose();
}
@override
Widget build(BuildContext context) {
// var isConnected = context.watch<ApiProvider>().isConnected;
// Glue the SettingsController to the MaterialApp.
//
// The ListenableBuilder Widget listens to the SettingsController for changes.
// Whenever the user updates their settings, the MaterialApp is rebuilt.
return ListenableBuilder(
listenable: context.watch<SettingsChangeProvider>(),
builder: (BuildContext context, Widget? child) {
@ -107,7 +98,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
const InputDecorationTheme(border: OutlineInputBorder())),
darkTheme: ThemeData.dark().copyWith(
colorScheme: ColorScheme.fromSeed(
brightness: Brightness.dark, // <-- the only line added
brightness: Brightness.dark,
seedColor: const Color(0xFF57CC99),
),
inputDecorationTheme: const InputDecorationTheme(
@ -117,9 +108,8 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
themeMode: context.watch<SettingsChangeProvider>().themeMode,
initialRoute: '/',
routes: {
"/": (context) => MyAppMainWidget(initialPage: 0),
"/chats": (context) => MyAppMainWidget(initialPage: 1)
// home: MyAppMainWidget(isConnected: isConnected, initialPage: 0),
"/": (context) => AppMainWidget(initialPage: 0),
"/chats": (context) => AppMainWidget(initialPage: 1)
},
);
},
@ -127,53 +117,49 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
}
}
class MyAppMainWidget extends StatefulWidget {
const MyAppMainWidget({super.key, required this.initialPage});
class AppMainWidget extends StatefulWidget {
const AppMainWidget({super.key, required this.initialPage});
final int initialPage;
@override
State<MyAppMainWidget> createState() => _MyAppMainWidgetState();
State<AppMainWidget> createState() => _AppMainWidgetState();
}
class _MyAppMainWidgetState extends State<MyAppMainWidget> {
Future<bool> _isUserCreated = isUserCreated();
bool _showOnboarding = true;
class _AppMainWidgetState extends State<AppMainWidget> {
Future<bool> userCreated = isUserCreated();
bool showOnboarding = true;
@override
Widget build(BuildContext context) {
return Stack(
children: [
FutureBuilder<bool>(
future: _isUserCreated,
future: userCreated,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data!) {
return HomeView(
initialPage: widget.initialPage,
);
}
if (_showOnboarding) {
return OnboardingView(
callbackOnSuccess: () {
setState(() {
_showOnboarding = false;
});
},
);
}
return RegisterView(
callbackOnSuccess: () {
setState(() {
_isUserCreated = isUserCreated();
});
},
);
} else {
if (!snapshot.hasData) {
return Center(child: Container());
}
if (snapshot.data!) {
return HomeView(
initialPage: widget.initialPage,
);
}
return showOnboarding
? OnboardingView(
callbackOnSuccess: () {
setState(() {
showOnboarding = false;
});
},
)
: RegisterView(
callbackOnSuccess: () {
setState(() {
userCreated = isUserCreated();
});
},
);
},
),
],

View file

@ -1,4 +1,5 @@
import 'package:camera/camera.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart';
@ -10,7 +11,7 @@ import 'package:twonly/src/providers/settings_change_provider.dart';
import 'package:twonly/src/services/fcm_service.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'src/app.dart';
import 'app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -32,13 +33,15 @@ void main() async {
twonlyDatabase = TwonlyDatabase();
await twonlyDatabase.messagesDao.appRestarted();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => settingsController),
ChangeNotifierProvider(create: (_) => ConnectionChangeProvider()),
],
child: MyApp(),
child: App(),
),
);
}

View file

@ -11,7 +11,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDatabase>
with _$MessagesDaoMixin {
// this constructor is required so that the main database can create an instance
// of this object.
MessagesDao(TwonlyDatabase db) : super(db);
MessagesDao(super.db);
Stream<List<Message>> watchMessageNotOpened(int contactId) {
return (select(messages)

View file

@ -0,0 +1,933 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_de.dart';
import 'app_localizations_en.dart';
// ignore_for_file: type=lint
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```dart
/// import 'generated/app_localizations.dart';
///
/// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
/// supportedLocales: AppLocalizations.supportedLocales,
/// home: MyApplicationHome(),
/// );
/// ```
///
/// ## Update pubspec.yaml
///
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications
///
/// iOS applications define key application metadata, including supported
/// locales, in an Info.plist file that is built into the application bundle.
/// To configure the locales supported by your app, youll need to edit this
/// file.
///
/// First, open your projects ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// projects Runner folder.
///
/// Next, select the Information Property List item, select Add Item from the
/// Editor menu, then select Localizations from the pop-up menu.
///
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
///
/// Returns a list of localizations delegates containing this delegate along with
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
/// and GlobalWidgetsLocalizations.delegate.
///
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('de'),
Locale('en')
];
/// No description provided for @registerTitle.
///
/// In en, this message translates to:
/// **'Welcome to twonly!'**
String get registerTitle;
/// No description provided for @registerSlogan.
///
/// In en, this message translates to:
/// **'twonly, a privacy friendly way to connect with friends through secure, spontaneous image sharing'**
String get registerSlogan;
/// No description provided for @onboardingWelcomeTitle.
///
/// In en, this message translates to:
/// **'Welcome to twonly!'**
String get onboardingWelcomeTitle;
/// No description provided for @onboardingWelcomeBody.
///
/// In en, this message translates to:
/// **'Experience a private and secure way to stay in touch with friends by sharing instant pictures.'**
String get onboardingWelcomeBody;
/// No description provided for @onboardingE2eTitle.
///
/// In en, this message translates to:
/// **'Carefree sharing'**
String get onboardingE2eTitle;
/// No description provided for @onboardingE2eBody.
///
/// In en, this message translates to:
/// **'With end-to-end encryption, enjoy the peace of mind that only you and your friends can see the moments you share.'**
String get onboardingE2eBody;
/// No description provided for @onboardingFocusTitle.
///
/// In en, this message translates to:
/// **'Focus on sharing moments'**
String get onboardingFocusTitle;
/// No description provided for @onboardingFocusBody.
///
/// In en, this message translates to:
/// **'Say goodbye to addictive features! twonly was created for sharing moments, free from useless distractions or ads.'**
String get onboardingFocusBody;
/// No description provided for @onboardingSendTwonliesTitle.
///
/// In en, this message translates to:
/// **'Send twonlies'**
String get onboardingSendTwonliesTitle;
/// No description provided for @onboardingSendTwonliesBody.
///
/// In en, this message translates to:
/// **'Share moments securely with your partner. twonly ensures that only your partner can open it, keeping your moments with your partner a two(o)nly thing!'**
String get onboardingSendTwonliesBody;
/// No description provided for @onboardingNotProductTitle.
///
/// In en, this message translates to:
/// **'You are not the product!'**
String get onboardingNotProductTitle;
/// No description provided for @onboardingNotProductBody.
///
/// In en, this message translates to:
/// **'twonly is financed by a small monthly fee and not by selling your data.'**
String get onboardingNotProductBody;
/// No description provided for @onboardingBuyOneGetTwoTitle.
///
/// In en, this message translates to:
/// **'Buy one get two'**
String get onboardingBuyOneGetTwoTitle;
/// No description provided for @onboardingBuyOneGetTwoBody.
///
/// In en, this message translates to:
/// **'twonly always requires at least two people, which is why you receive a second free license for your twonly partner with your purchase.'**
String get onboardingBuyOneGetTwoBody;
/// No description provided for @onboardingGetStartedTitle.
///
/// In en, this message translates to:
/// **'Let\'s go!'**
String get onboardingGetStartedTitle;
/// No description provided for @onboardingGetStartedBody.
///
/// In en, this message translates to:
/// **'You can test twonly free of charge for 14 days, after that it costs either 1€/month or 9€/year.'**
String get onboardingGetStartedBody;
/// No description provided for @onboardingTryForFree.
///
/// In en, this message translates to:
/// **'Try for free'**
String get onboardingTryForFree;
/// No description provided for @registerUsernameSlogan.
///
/// In en, this message translates to:
/// **'Please select a username so others can find you!'**
String get registerUsernameSlogan;
/// No description provided for @registerUsernameDecoration.
///
/// In en, this message translates to:
/// **'Username'**
String get registerUsernameDecoration;
/// No description provided for @registerUsernameLimits.
///
/// In en, this message translates to:
/// **'Username must be 3 to 12 characters long, consisting only of letters (a-z) and numbers (0-9).'**
String get registerUsernameLimits;
/// No description provided for @registerSubmitButton.
///
/// In en, this message translates to:
/// **'Register now!'**
String get registerSubmitButton;
/// No description provided for @newMessageTitle.
///
/// In en, this message translates to:
/// **'New message'**
String get newMessageTitle;
/// No description provided for @chatsTapToSend.
///
/// In en, this message translates to:
/// **'Click to send your first image'**
String get chatsTapToSend;
/// No description provided for @cameraPreviewSendTo.
///
/// In en, this message translates to:
/// **'Send to'**
String get cameraPreviewSendTo;
/// No description provided for @shareImageTitle.
///
/// In en, this message translates to:
/// **'Share with'**
String get shareImageTitle;
/// No description provided for @shareImageBestFriends.
///
/// In en, this message translates to:
/// **'Best friends'**
String get shareImageBestFriends;
/// No description provided for @shareImagedEditorSendImage.
///
/// In en, this message translates to:
/// **'Send'**
String get shareImagedEditorSendImage;
/// No description provided for @shareImagedEditorShareWith.
///
/// In en, this message translates to:
/// **'Share with'**
String get shareImagedEditorShareWith;
/// No description provided for @shareImagedEditorSaveImage.
///
/// In en, this message translates to:
/// **'Save'**
String get shareImagedEditorSaveImage;
/// No description provided for @shareImagedEditorSavedImage.
///
/// In en, this message translates to:
/// **'Saved'**
String get shareImagedEditorSavedImage;
/// No description provided for @shareImageSearchAllContacts.
///
/// In en, this message translates to:
/// **'Search all contacts'**
String get shareImageSearchAllContacts;
/// No description provided for @startNewChatTitle.
///
/// In en, this message translates to:
/// **'Select Contact'**
String get startNewChatTitle;
/// No description provided for @startNewChatNewContact.
///
/// In en, this message translates to:
/// **'New Contact'**
String get startNewChatNewContact;
/// No description provided for @startNewChatYourContacts.
///
/// In en, this message translates to:
/// **'Your Contacts'**
String get startNewChatYourContacts;
/// No description provided for @shareImageAllUsers.
///
/// In en, this message translates to:
/// **'All contacts'**
String get shareImageAllUsers;
/// No description provided for @shareImageAllTwonlyWarning.
///
/// In en, this message translates to:
/// **'twonlies can only be send to verified contacts!'**
String get shareImageAllTwonlyWarning;
/// No description provided for @searchUsernameInput.
///
/// In en, this message translates to:
/// **'Username'**
String get searchUsernameInput;
/// No description provided for @searchUsernameTitle.
///
/// In en, this message translates to:
/// **'Search username'**
String get searchUsernameTitle;
/// No description provided for @searchUserNamePending.
///
/// In en, this message translates to:
/// **'Pending'**
String get searchUserNamePending;
/// No description provided for @searchUserNameBlockUserTooltip.
///
/// In en, this message translates to:
/// **'Block the user without informing.'**
String get searchUserNameBlockUserTooltip;
/// No description provided for @searchUserNameRejectUserTooltip.
///
/// In en, this message translates to:
/// **'Reject the request and let the requester know.'**
String get searchUserNameRejectUserTooltip;
/// No description provided for @searchUserNameArchiveUserTooltip.
///
/// In en, this message translates to:
/// **'Archive the user. He will appear again as soon as he accepts your request.'**
String get searchUserNameArchiveUserTooltip;
/// No description provided for @searchUsernameNotFound.
///
/// In en, this message translates to:
/// **'Username not found'**
String get searchUsernameNotFound;
/// No description provided for @searchUsernameNotFoundBody.
///
/// In en, this message translates to:
/// **'There is no user with the username \"{username}\" registered'**
String searchUsernameNotFoundBody(Object username);
/// No description provided for @searchUsernameNewFollowerTitle.
///
/// In en, this message translates to:
/// **'Follow requests'**
String get searchUsernameNewFollowerTitle;
/// No description provided for @searchUsernameQrCodeBtn.
///
/// In en, this message translates to:
/// **'Scan QR code'**
String get searchUsernameQrCodeBtn;
/// No description provided for @chatListViewSearchUserNameBtn.
///
/// In en, this message translates to:
/// **'Add your first twonly contact!'**
String get chatListViewSearchUserNameBtn;
/// No description provided for @chatListViewSendFirstTwonly.
///
/// In en, this message translates to:
/// **'Send your first twonly!'**
String get chatListViewSendFirstTwonly;
/// No description provided for @chatListDetailInput.
///
/// In en, this message translates to:
/// **'Type a message'**
String get chatListDetailInput;
/// No description provided for @contextMenuVerifyUser.
///
/// In en, this message translates to:
/// **'Verify'**
String get contextMenuVerifyUser;
/// No description provided for @contextMenuArchiveUser.
///
/// In en, this message translates to:
/// **'Archive'**
String get contextMenuArchiveUser;
/// No description provided for @contextMenuUndoArchiveUser.
///
/// In en, this message translates to:
/// **'Undo archiving'**
String get contextMenuUndoArchiveUser;
/// No description provided for @contextMenuOpenChat.
///
/// In en, this message translates to:
/// **'Open chat'**
String get contextMenuOpenChat;
/// No description provided for @contextMenuSendImage.
///
/// In en, this message translates to:
/// **'Send image'**
String get contextMenuSendImage;
/// No description provided for @mediaViewerAuthReason.
///
/// In en, this message translates to:
/// **'Please authenticate to see this twonly!'**
String get mediaViewerAuthReason;
/// No description provided for @messageSendState_Received.
///
/// In en, this message translates to:
/// **'Received'**
String get messageSendState_Received;
/// No description provided for @messageSendState_Opened.
///
/// In en, this message translates to:
/// **'Opened'**
String get messageSendState_Opened;
/// No description provided for @messageSendState_Send.
///
/// In en, this message translates to:
/// **'Sent'**
String get messageSendState_Send;
/// No description provided for @messageSendState_Sending.
///
/// In en, this message translates to:
/// **'Sending'**
String get messageSendState_Sending;
/// No description provided for @messageSendState_TapToLoad.
///
/// In en, this message translates to:
/// **'Tap to load'**
String get messageSendState_TapToLoad;
/// No description provided for @messageSendState_Loading.
///
/// In en, this message translates to:
/// **'Downloading'**
String get messageSendState_Loading;
/// No description provided for @messageStoredInGalery.
///
/// In en, this message translates to:
/// **'Stored in gallery'**
String get messageStoredInGalery;
/// No description provided for @imageEditorDrawOk.
///
/// In en, this message translates to:
/// **'Take drawing'**
String get imageEditorDrawOk;
/// No description provided for @settingsTitle.
///
/// In en, this message translates to:
/// **'Settings'**
String get settingsTitle;
/// No description provided for @settingsChats.
///
/// In en, this message translates to:
/// **'Chats'**
String get settingsChats;
/// No description provided for @settingsPreSelectedReactions.
///
/// In en, this message translates to:
/// **'Preselected reaction emojis'**
String get settingsPreSelectedReactions;
/// No description provided for @settingsPreSelectedReactionsError.
///
/// In en, this message translates to:
/// **'A maximum of 12 reactions can be selected.'**
String get settingsPreSelectedReactionsError;
/// No description provided for @settingsProfile.
///
/// In en, this message translates to:
/// **'Profile'**
String get settingsProfile;
/// No description provided for @settingsProfileCustomizeAvatar.
///
/// In en, this message translates to:
/// **'Customize your avatar'**
String get settingsProfileCustomizeAvatar;
/// No description provided for @settingsProfileEditDisplayName.
///
/// In en, this message translates to:
/// **'Displayname'**
String get settingsProfileEditDisplayName;
/// No description provided for @settingsProfileEditDisplayNameNew.
///
/// In en, this message translates to:
/// **'New Displayname'**
String get settingsProfileEditDisplayNameNew;
/// No description provided for @settingsAccount.
///
/// In en, this message translates to:
/// **'Konto'**
String get settingsAccount;
/// No description provided for @settingsSubscription.
///
/// In en, this message translates to:
/// **'Subscription'**
String get settingsSubscription;
/// No description provided for @settingsAppearance.
///
/// In en, this message translates to:
/// **'Appearance'**
String get settingsAppearance;
/// No description provided for @settingsPrivacy.
///
/// In en, this message translates to:
/// **'Privacy'**
String get settingsPrivacy;
/// No description provided for @settingsPrivacyBlockUsers.
///
/// In en, this message translates to:
/// **'Block users'**
String get settingsPrivacyBlockUsers;
/// No description provided for @settingsPrivacyBlockUsersDesc.
///
/// In en, this message translates to:
/// **'Blocked users will not be able to communicate with you. You can unblock a blocked user at any time.'**
String get settingsPrivacyBlockUsersDesc;
/// No description provided for @settingsPrivacyBlockUsersCount.
///
/// In en, this message translates to:
/// **'{len} contact(s)'**
String settingsPrivacyBlockUsersCount(Object len);
/// No description provided for @settingsNotification.
///
/// In en, this message translates to:
/// **'Notification'**
String get settingsNotification;
/// No description provided for @settingsNotifyTroubleshooting.
///
/// In en, this message translates to:
/// **'Troubleshooting'**
String get settingsNotifyTroubleshooting;
/// No description provided for @settingsNotifyTroubleshootingDesc.
///
/// In en, this message translates to:
/// **'Click here if you have problems receiving push notifications.'**
String get settingsNotifyTroubleshootingDesc;
/// No description provided for @settingsNotifyTroubleshootingNoProblem.
///
/// In en, this message translates to:
/// **'No problem detected'**
String get settingsNotifyTroubleshootingNoProblem;
/// No description provided for @settingsNotifyTroubleshootingNoProblemDesc.
///
/// In en, this message translates to:
/// **'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.'**
String get settingsNotifyTroubleshootingNoProblemDesc;
/// No description provided for @settingsHelp.
///
/// In en, this message translates to:
/// **'Help'**
String get settingsHelp;
/// No description provided for @settingsHelpDiagnostics.
///
/// In en, this message translates to:
/// **'Diagnostic protocol'**
String get settingsHelpDiagnostics;
/// No description provided for @settingsHelpSupport.
///
/// In en, this message translates to:
/// **'Support Center'**
String get settingsHelpSupport;
/// No description provided for @settingsHelpVersion.
///
/// In en, this message translates to:
/// **'Version'**
String get settingsHelpVersion;
/// No description provided for @settingsHelpLicenses.
///
/// In en, this message translates to:
/// **'Licenses (Source-Code)'**
String get settingsHelpLicenses;
/// No description provided for @settingsHelpCredits.
///
/// In en, this message translates to:
/// **'Licenses (Images)'**
String get settingsHelpCredits;
/// No description provided for @settingsHelpLegal.
///
/// In en, this message translates to:
/// **'Imprint, Terms & Privacy Policy'**
String get settingsHelpLegal;
/// No description provided for @settingsAppearanceTheme.
///
/// In en, this message translates to:
/// **'Theme'**
String get settingsAppearanceTheme;
/// No description provided for @settingsAccountDeleteAccount.
///
/// In en, this message translates to:
/// **'Delete account'**
String get settingsAccountDeleteAccount;
/// No description provided for @settingsAccountDeleteModalTitle.
///
/// In en, this message translates to:
/// **'Are you sure?'**
String get settingsAccountDeleteModalTitle;
/// No description provided for @settingsAccountDeleteModalBody.
///
/// In en, this message translates to:
/// **'Your account will be deleted. There is no change to restore it.'**
String get settingsAccountDeleteModalBody;
/// No description provided for @contactVerifyNumberTitle.
///
/// In en, this message translates to:
/// **'Verify safety number'**
String get contactVerifyNumberTitle;
/// No description provided for @contactVerifyNumberMarkAsVerified.
///
/// In en, this message translates to:
/// **'Mark as verified'**
String get contactVerifyNumberMarkAsVerified;
/// No description provided for @contactVerifyNumberClearVerification.
///
/// In en, this message translates to:
/// **'Clear verification'**
String get contactVerifyNumberClearVerification;
/// No description provided for @contactVerifyNumberLongDesc.
///
/// In en, this message translates to:
/// **'To verify the end-to-end encryption with {username}, compare the numbers with their device. The person can also scan your code with their device.'**
String contactVerifyNumberLongDesc(Object username);
/// No description provided for @contactNickname.
///
/// In en, this message translates to:
/// **'Nickname'**
String get contactNickname;
/// No description provided for @contactNicknameNew.
///
/// In en, this message translates to:
/// **'New nickname'**
String get contactNicknameNew;
/// No description provided for @deleteAllContactMessages.
///
/// In en, this message translates to:
/// **'Delete all messages'**
String get deleteAllContactMessages;
/// No description provided for @deleteAllContactMessagesBody.
///
/// In en, this message translates to:
/// **'This will remove all messages in your chat with {username}. This will NOT delete the messages stored at {username}s device!'**
String deleteAllContactMessagesBody(Object username);
/// No description provided for @contactBlock.
///
/// In en, this message translates to:
/// **'Block'**
String get contactBlock;
/// No description provided for @contactBlockTitle.
///
/// In en, this message translates to:
/// **'Block {username}'**
String contactBlockTitle(Object username);
/// No description provided for @contactBlockBody.
///
/// In en, this message translates to:
/// **'A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.'**
String get contactBlockBody;
/// No description provided for @undo.
///
/// In en, this message translates to:
/// **'Undo'**
String get undo;
/// No description provided for @redo.
///
/// In en, this message translates to:
/// **'Redo'**
String get redo;
/// No description provided for @next.
///
/// In en, this message translates to:
/// **'Next'**
String get next;
/// No description provided for @close.
///
/// In en, this message translates to:
/// **'Close'**
String get close;
/// No description provided for @cancel.
///
/// In en, this message translates to:
/// **'Cancel'**
String get cancel;
/// No description provided for @ok.
///
/// In en, this message translates to:
/// **'Ok'**
String get ok;
/// No description provided for @switchFrontAndBackCamera.
///
/// In en, this message translates to:
/// **'Switch between front and back camera.'**
String get switchFrontAndBackCamera;
/// No description provided for @addTextItem.
///
/// In en, this message translates to:
/// **'Text'**
String get addTextItem;
/// No description provided for @protectAsARealTwonly.
///
/// In en, this message translates to:
/// **'Send as real twonly!'**
String get protectAsARealTwonly;
/// No description provided for @addDrawing.
///
/// In en, this message translates to:
/// **'Drawing'**
String get addDrawing;
/// No description provided for @addEmoji.
///
/// In en, this message translates to:
/// **'Emoji'**
String get addEmoji;
/// No description provided for @toggleFlashLight.
///
/// In en, this message translates to:
/// **'Toggle the flash light'**
String get toggleFlashLight;
/// No description provided for @toggleHighQuality.
///
/// In en, this message translates to:
/// **'Toggle better resolution'**
String get toggleHighQuality;
/// No description provided for @userFound.
///
/// In en, this message translates to:
/// **'User found'**
String get userFound;
/// No description provided for @userFoundBody.
///
/// In en, this message translates to:
/// **'Do you want to create a follow request?'**
String get userFoundBody;
/// No description provided for @searchUsernameNotFoundLong.
///
/// In en, this message translates to:
/// **'\"{username}\" is not a twonly user. Please check the username and try again.'**
String searchUsernameNotFoundLong(Object username);
/// No description provided for @errorUnknown.
///
/// In en, this message translates to:
/// **'An unexpected error has occurred. Please try again later.'**
String get errorUnknown;
/// No description provided for @errorBadRequest.
///
/// In en, this message translates to:
/// **'The request could not be understood by the server due to malformed syntax. Please check your input and try again.'**
String get errorBadRequest;
/// No description provided for @errorTooManyRequests.
///
/// In en, this message translates to:
/// **'You have made too many requests in a short period. Please wait a moment before trying again.'**
String get errorTooManyRequests;
/// No description provided for @errorInternalError.
///
/// In en, this message translates to:
/// **'The server is currently not available. Please try again later.'**
String get errorInternalError;
/// No description provided for @errorInvalidInvitationCode.
///
/// In en, this message translates to:
/// **'The invitation code you provided is invalid. Please check the code and try again.'**
String get errorInvalidInvitationCode;
/// No description provided for @errorUsernameAlreadyTaken.
///
/// In en, this message translates to:
/// **'The username you want to use is already taken. Please choose a different username.'**
String get errorUsernameAlreadyTaken;
/// No description provided for @errorSignatureNotValid.
///
/// In en, this message translates to:
/// **'The provided signature is not valid. Please check your credentials and try again.'**
String get errorSignatureNotValid;
/// No description provided for @errorUsernameNotFound.
///
/// In en, this message translates to:
/// **'The username you entered does not exist. Please check the spelling or create a new account.'**
String get errorUsernameNotFound;
/// No description provided for @errorUsernameNotValid.
///
/// In en, this message translates to:
/// **'The username you provided does not meet the required criteria. Please choose a valid username.'**
String get errorUsernameNotValid;
/// No description provided for @errorInvalidPublicKey.
///
/// In en, this message translates to:
/// **'The public key you provided is invalid. Please check the key and try again.'**
String get errorInvalidPublicKey;
/// No description provided for @errorSessionAlreadyAuthenticated.
///
/// In en, this message translates to:
/// **'You are already logged in. Please log out if you want to log in with a different account.'**
String get errorSessionAlreadyAuthenticated;
/// No description provided for @errorSessionNotAuthenticated.
///
/// In en, this message translates to:
/// **'Your session is not authenticated. Please log in to continue.'**
String get errorSessionNotAuthenticated;
/// No description provided for @errorOnlyOneSessionAllowed.
///
/// In en, this message translates to:
/// **'Only one active session is allowed per user. Please log out from other devices to continue.'**
String get errorOnlyOneSessionAllowed;
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) => <String>['de', 'en'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de': return AppLocalizationsDe();
case 'en': return AppLocalizationsEn();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.'
);
}

View file

@ -0,0 +1,424 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for German (`de`).
class AppLocalizationsDe extends AppLocalizations {
AppLocalizationsDe([String locale = 'de']) : super(locale);
@override
String get registerTitle => 'Willkommen bei twonly!';
@override
String get registerSlogan => 'twonly, eine private und sichere Möglichkeit um mit Freunden in Kontakt zu bleiben.';
@override
String get onboardingWelcomeTitle => 'Willkommen bei twonly!';
@override
String get onboardingWelcomeBody => 'Erlebe eine private und sichere Möglichkeit mit Freunden in Kontakt zu bleiben, indem du spontane Bilder teilst.';
@override
String get onboardingE2eTitle => 'Unbekümmert teilen';
@override
String get onboardingE2eBody => 'Genieße durch die Ende-zu-Ende-Verschlüsselung die Gewissheit, dass nur du und deine Freunde die geteilten Momente sehen können.';
@override
String get onboardingFocusTitle => 'Fokussiere dich auf das Teilen von Momenten';
@override
String get onboardingFocusBody => 'Verabschiede dich von süchtig machenden Funktionen! twonly wurde für das Teilen von Momenten ohne nutzlose Ablenkungen oder Werbung entwickelt.';
@override
String get onboardingSendTwonliesTitle => 'Twonlies senden';
@override
String get onboardingSendTwonliesBody => 'Teile Momente sicher mit deinem Partner. twonly stellt sicher, dass nur dein Partner sie öffnen kann, sodass deine Momente mit deinem Partner eine two(o)nly Sache bleiben!';
@override
String get onboardingNotProductTitle => 'Du bist nicht das Produkt!';
@override
String get onboardingNotProductBody => 'twonly finanziert sich durch eine geringe monatliche Gebühr und nicht durch den Verkauf deiner Daten.';
@override
String get onboardingBuyOneGetTwoTitle => 'Kaufe eins, bekomme zwei';
@override
String get onboardingBuyOneGetTwoBody => 'twonly benötigt immer mindestens zwei Personen, daher erhältst du beim Kauf eine zweite kostenlose Lizenz für deinen twonly-Partner.';
@override
String get onboardingGetStartedTitle => 'Auf geht\'s';
@override
String get onboardingGetStartedBody => 'Du kannst twonly 14 Tage lang kostenlos testen, danach kostet es entweder 1€/Monat oder 9€/Jahr.';
@override
String get onboardingTryForFree => 'Kostenlos testen';
@override
String get registerUsernameSlogan => 'Bitte wähle einen Benutzernamen, damit dich andere finden können!';
@override
String get registerUsernameDecoration => 'Benutzername';
@override
String get registerUsernameLimits => 'Der Benutzername muss 3 bis 12 Zeichen lang sein und darf nur aus Buchstaben (a-z) und Zahlen (0-9) bestehen.';
@override
String get registerSubmitButton => 'Jetzt registrieren!';
@override
String get newMessageTitle => 'Neue Nachricht';
@override
String get chatsTapToSend => 'Klicke, um dein erstes Bild zu teilen.';
@override
String get cameraPreviewSendTo => 'Senden an';
@override
String get shareImageTitle => 'Teilen mit';
@override
String get shareImageBestFriends => 'Beste Freunde';
@override
String get shareImagedEditorSendImage => 'Senden';
@override
String get shareImagedEditorShareWith => 'Teilen mit';
@override
String get shareImagedEditorSaveImage => 'Speichern';
@override
String get shareImagedEditorSavedImage => 'Gespeichert';
@override
String get shareImageSearchAllContacts => 'Alle Kontakte durchsuchen';
@override
String get startNewChatTitle => 'Kontakt wählen';
@override
String get startNewChatNewContact => 'Neuer Kontakt';
@override
String get startNewChatYourContacts => 'Deine Kontakte';
@override
String get shareImageAllUsers => 'Alle Kontakte';
@override
String get shareImageAllTwonlyWarning => 'Twonlies können nur an verifizierte Kontakte gesendet werden!';
@override
String get searchUsernameInput => 'Benutzername';
@override
String get searchUsernameTitle => 'Benutzernamen suchen';
@override
String get searchUserNamePending => 'Ausstehend';
@override
String get searchUserNameBlockUserTooltip => 'Benutzer ohne Benachrichtigung blockieren.';
@override
String get searchUserNameRejectUserTooltip => 'Die Anfrage ablehnen und den Anfragenden informieren.';
@override
String get searchUserNameArchiveUserTooltip => 'Benutzer archivieren. Du wirst informiert sobald er deine Anfrage akzeptiert.';
@override
String get searchUsernameNotFound => 'Benutzername nicht gefunden';
@override
String searchUsernameNotFoundBody(Object username) {
return 'Es wurde kein Benutzer mit dem Benutzernamen \"$username\" gefunden.';
}
@override
String get searchUsernameNewFollowerTitle => 'Folgeanfragen';
@override
String get searchUsernameQrCodeBtn => 'QR-Code scannen';
@override
String get chatListViewSearchUserNameBtn => 'Füge deinen ersten twonly-Kontakt hinzu!';
@override
String get chatListViewSendFirstTwonly => 'Sende dein erstes twonly!';
@override
String get chatListDetailInput => 'Nachricht eingeben';
@override
String get contextMenuVerifyUser => 'Verifizieren';
@override
String get contextMenuArchiveUser => 'Archivieren';
@override
String get contextMenuUndoArchiveUser => 'Archivierung aufheben';
@override
String get contextMenuOpenChat => 'Chat';
@override
String get contextMenuSendImage => 'Bild senden';
@override
String get mediaViewerAuthReason => 'Bitte authentifiziere dich, um diesen twonly zu sehen!';
@override
String get messageSendState_Received => 'Empfangen';
@override
String get messageSendState_Opened => 'Geöffnet';
@override
String get messageSendState_Send => 'Gesendet';
@override
String get messageSendState_Sending => 'Wird gesendet';
@override
String get messageSendState_TapToLoad => 'Tippe zum Laden';
@override
String get messageSendState_Loading => 'Herunterladen';
@override
String get messageStoredInGalery => 'In Gallerie gespeichert';
@override
String get imageEditorDrawOk => 'Zeichnung machen';
@override
String get settingsTitle => 'Einstellungen';
@override
String get settingsChats => 'Chats';
@override
String get settingsPreSelectedReactions => 'Vorgewählte Reaktions-Emojis';
@override
String get settingsPreSelectedReactionsError => 'Es können maximal 12 Reaktionen ausgewählt werden.';
@override
String get settingsProfile => 'Profil';
@override
String get settingsProfileCustomizeAvatar => 'Avatar anpassen';
@override
String get settingsProfileEditDisplayName => 'Anzeigename';
@override
String get settingsProfileEditDisplayNameNew => 'Neuer Anzeigename';
@override
String get settingsAccount => 'Konto';
@override
String get settingsSubscription => 'Abonnement';
@override
String get settingsAppearance => 'Erscheinungsbild';
@override
String get settingsPrivacy => 'Datenschutz';
@override
String get settingsPrivacyBlockUsers => 'Benutzer blockieren';
@override
String get settingsPrivacyBlockUsersDesc => 'Blockierte Benutzer können nicht mit dir kommunizieren. Du kannst einen blockierten Benutzer jederzeit wieder entsperren.';
@override
String settingsPrivacyBlockUsersCount(Object len) {
return '$len Kontakt(e)';
}
@override
String get settingsNotification => 'Benachrichtigung';
@override
String get settingsNotifyTroubleshooting => 'Fehlersuche';
@override
String get settingsNotifyTroubleshootingDesc => 'Hier klicken, wenn Probleme beim Empfang von Push-Benachrichtigungen auftreten.';
@override
String get settingsNotifyTroubleshootingNoProblem => 'Kein Problem festgestellt';
@override
String get settingsNotifyTroubleshootingNoProblemDesc => 'Klicke auf OK, um eine Testbenachrichtigung zu erhalten. Wenn du auch nach 10 Minuten warten keine Nachricht erhältst, sende uns bitte dein Diagnoseprotokoll unter Einstellungen > Hilfe > Diagnoseprotokoll, damit wir uns das Problem ansehen können.';
@override
String get settingsHelp => 'Hilfe';
@override
String get settingsHelpDiagnostics => 'Diagnoseprotokoll';
@override
String get settingsHelpSupport => 'Support-Center';
@override
String get settingsHelpVersion => 'Version';
@override
String get settingsHelpLicenses => 'Lizenzen (Source-Code)';
@override
String get settingsHelpCredits => 'Lizenzen (Bilder)';
@override
String get settingsHelpLegal => 'Impressum, Nutzungsbedingungen & Datenschutzrichtlinie';
@override
String get settingsAppearanceTheme => 'Theme';
@override
String get settingsAccountDeleteAccount => 'Konto löschen';
@override
String get settingsAccountDeleteModalTitle => 'Bist du sicher?';
@override
String get settingsAccountDeleteModalBody => 'Dein Konto wird gelöscht. Es gibt keine Möglichkeit, es wiederherzustellen.';
@override
String get contactVerifyNumberTitle => 'Sicherheitsnummer verifizieren';
@override
String get contactVerifyNumberMarkAsVerified => 'Als verifiziert markieren';
@override
String get contactVerifyNumberClearVerification => 'Verifizierung aufheben';
@override
String contactVerifyNumberLongDesc(Object username) {
return 'Um die Ende-zu-Ende-Verschlüsselung mit $username zu verifizieren, vergleiche die Zahlen mit ihrem Gerät. Die Person kann auch deinen Code mit ihrem Gerät scannen.';
}
@override
String get contactNickname => 'Spitzname';
@override
String get contactNicknameNew => 'Neuer Spitzname';
@override
String get deleteAllContactMessages => 'Alle Nachrichten löschen';
@override
String deleteAllContactMessagesBody(Object username) {
return 'Dadurch werden alle Nachrichten in deinem Chat mit $username gelöscht. Dies löscht NICHT die auf dem Gerät von $username gespeicherten Nachrichten!';
}
@override
String get contactBlock => 'Blockieren';
@override
String contactBlockTitle(Object username) {
return 'Blockiere $username';
}
@override
String get contactBlockBody => 'Ein blockierter Benutzer kann dir keine Nachrichten mehr senden, und sein Profil ist nicht mehr sichtbar. Um die Blockierung eines Benutzers aufzuheben, navigiere einfach zu Einstellungen > Datenschutz > Blockierte Benutzer.';
@override
String get undo => 'Rückgängig';
@override
String get redo => 'Wiederholen';
@override
String get next => 'Weiter';
@override
String get close => 'Schließen';
@override
String get cancel => 'Abbrechen';
@override
String get ok => 'Ok';
@override
String get switchFrontAndBackCamera => 'Zwischen Front- und Rückkamera wechseln.';
@override
String get addTextItem => 'Text';
@override
String get protectAsARealTwonly => 'Als echtes twonly senden!';
@override
String get addDrawing => 'Zeichnung';
@override
String get addEmoji => 'Emoji';
@override
String get toggleFlashLight => 'Taschenlampe umschalten';
@override
String get toggleHighQuality => 'Bessere Auflösung umschalten';
@override
String get userFound => 'Benutzer gefunden';
@override
String get userFoundBody => 'Möchtest du eine Folgeanfrage stellen?';
@override
String searchUsernameNotFoundLong(Object username) {
return '\"$username\" ist kein twonly-Benutzer. Bitte überprüfe den Benutzernamen und versuche es erneut.';
}
@override
String get errorUnknown => 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es später erneut.';
@override
String get errorBadRequest => 'Die Anfrage konnte vom Server aufgrund einer fehlerhaften Syntax nicht verstanden werden. Bitte überprüfe deine Eingabe und versuche es erneut.';
@override
String get errorTooManyRequests => 'Du hast in kurzer Zeit zu viele Anfragen gestellt. Bitte warte einen Moment, bevor du es erneut versuchst.';
@override
String get errorInternalError => 'Der Server ist derzeit nicht verfügbar. Bitte versuche es später erneut.';
@override
String get errorInvalidInvitationCode => 'Der von dir angegebene Einladungscode ist ungültig. Bitte überprüfe den Code und versuche es erneut.';
@override
String get errorUsernameAlreadyTaken => 'Der Benutzername, den du verwenden möchtest, ist bereits vergeben. Bitte wähle einen anderen Benutzernamen.';
@override
String get errorSignatureNotValid => 'Die bereitgestellte Signatur ist nicht gültig. Bitte überprüfe deine Anmeldeinformationen und versuche es erneut.';
@override
String get errorUsernameNotFound => 'Der eingegebene Benutzername existiert nicht. Bitte überprüfe die Schreibweise oder erstelle ein neues Konto.';
@override
String get errorUsernameNotValid => 'Der von dir angegebene Benutzername entspricht nicht den erforderlichen Kriterien. Bitte wähle einen gültigen Benutzernamen.';
@override
String get errorInvalidPublicKey => 'Der von dir angegebene öffentliche Schlüssel ist ungültig. Bitte überprüfe den Schlüssel und versuche es erneut.';
@override
String get errorSessionAlreadyAuthenticated => 'Du bist bereits angemeldet. Bitte melde dich ab, wenn du dich mit einem anderen Konto anmelden möchtest.';
@override
String get errorSessionNotAuthenticated => 'Deine Sitzung ist nicht authentifiziert. Bitte melde dich an, um fortzufahren.';
@override
String get errorOnlyOneSessionAllowed => 'Es ist nur eine aktive Sitzung pro Benutzer erlaubt. Bitte melde dich von anderen Geräten ab, um fortzufahren.';
}

View file

@ -0,0 +1,424 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get registerTitle => 'Welcome to twonly!';
@override
String get registerSlogan => 'twonly, a privacy friendly way to connect with friends through secure, spontaneous image sharing';
@override
String get onboardingWelcomeTitle => 'Welcome to twonly!';
@override
String get onboardingWelcomeBody => 'Experience a private and secure way to stay in touch with friends by sharing instant pictures.';
@override
String get onboardingE2eTitle => 'Carefree sharing';
@override
String get onboardingE2eBody => 'With end-to-end encryption, enjoy the peace of mind that only you and your friends can see the moments you share.';
@override
String get onboardingFocusTitle => 'Focus on sharing moments';
@override
String get onboardingFocusBody => 'Say goodbye to addictive features! twonly was created for sharing moments, free from useless distractions or ads.';
@override
String get onboardingSendTwonliesTitle => 'Send twonlies';
@override
String get onboardingSendTwonliesBody => 'Share moments securely with your partner. twonly ensures that only your partner can open it, keeping your moments with your partner a two(o)nly thing!';
@override
String get onboardingNotProductTitle => 'You are not the product!';
@override
String get onboardingNotProductBody => 'twonly is financed by a small monthly fee and not by selling your data.';
@override
String get onboardingBuyOneGetTwoTitle => 'Buy one get two';
@override
String get onboardingBuyOneGetTwoBody => 'twonly always requires at least two people, which is why you receive a second free license for your twonly partner with your purchase.';
@override
String get onboardingGetStartedTitle => 'Let\'s go!';
@override
String get onboardingGetStartedBody => 'You can test twonly free of charge for 14 days, after that it costs either 1€/month or 9€/year.';
@override
String get onboardingTryForFree => 'Try for free';
@override
String get registerUsernameSlogan => 'Please select a username so others can find you!';
@override
String get registerUsernameDecoration => 'Username';
@override
String get registerUsernameLimits => 'Username must be 3 to 12 characters long, consisting only of letters (a-z) and numbers (0-9).';
@override
String get registerSubmitButton => 'Register now!';
@override
String get newMessageTitle => 'New message';
@override
String get chatsTapToSend => 'Click to send your first image';
@override
String get cameraPreviewSendTo => 'Send to';
@override
String get shareImageTitle => 'Share with';
@override
String get shareImageBestFriends => 'Best friends';
@override
String get shareImagedEditorSendImage => 'Send';
@override
String get shareImagedEditorShareWith => 'Share with';
@override
String get shareImagedEditorSaveImage => 'Save';
@override
String get shareImagedEditorSavedImage => 'Saved';
@override
String get shareImageSearchAllContacts => 'Search all contacts';
@override
String get startNewChatTitle => 'Select Contact';
@override
String get startNewChatNewContact => 'New Contact';
@override
String get startNewChatYourContacts => 'Your Contacts';
@override
String get shareImageAllUsers => 'All contacts';
@override
String get shareImageAllTwonlyWarning => 'twonlies can only be send to verified contacts!';
@override
String get searchUsernameInput => 'Username';
@override
String get searchUsernameTitle => 'Search username';
@override
String get searchUserNamePending => 'Pending';
@override
String get searchUserNameBlockUserTooltip => 'Block the user without informing.';
@override
String get searchUserNameRejectUserTooltip => 'Reject the request and let the requester know.';
@override
String get searchUserNameArchiveUserTooltip => 'Archive the user. He will appear again as soon as he accepts your request.';
@override
String get searchUsernameNotFound => 'Username not found';
@override
String searchUsernameNotFoundBody(Object username) {
return 'There is no user with the username \"$username\" registered';
}
@override
String get searchUsernameNewFollowerTitle => 'Follow requests';
@override
String get searchUsernameQrCodeBtn => 'Scan QR code';
@override
String get chatListViewSearchUserNameBtn => 'Add your first twonly contact!';
@override
String get chatListViewSendFirstTwonly => 'Send your first twonly!';
@override
String get chatListDetailInput => 'Type a message';
@override
String get contextMenuVerifyUser => 'Verify';
@override
String get contextMenuArchiveUser => 'Archive';
@override
String get contextMenuUndoArchiveUser => 'Undo archiving';
@override
String get contextMenuOpenChat => 'Open chat';
@override
String get contextMenuSendImage => 'Send image';
@override
String get mediaViewerAuthReason => 'Please authenticate to see this twonly!';
@override
String get messageSendState_Received => 'Received';
@override
String get messageSendState_Opened => 'Opened';
@override
String get messageSendState_Send => 'Sent';
@override
String get messageSendState_Sending => 'Sending';
@override
String get messageSendState_TapToLoad => 'Tap to load';
@override
String get messageSendState_Loading => 'Downloading';
@override
String get messageStoredInGalery => 'Stored in gallery';
@override
String get imageEditorDrawOk => 'Take drawing';
@override
String get settingsTitle => 'Settings';
@override
String get settingsChats => 'Chats';
@override
String get settingsPreSelectedReactions => 'Preselected reaction emojis';
@override
String get settingsPreSelectedReactionsError => 'A maximum of 12 reactions can be selected.';
@override
String get settingsProfile => 'Profile';
@override
String get settingsProfileCustomizeAvatar => 'Customize your avatar';
@override
String get settingsProfileEditDisplayName => 'Displayname';
@override
String get settingsProfileEditDisplayNameNew => 'New Displayname';
@override
String get settingsAccount => 'Konto';
@override
String get settingsSubscription => 'Subscription';
@override
String get settingsAppearance => 'Appearance';
@override
String get settingsPrivacy => 'Privacy';
@override
String get settingsPrivacyBlockUsers => 'Block users';
@override
String get settingsPrivacyBlockUsersDesc => 'Blocked users will not be able to communicate with you. You can unblock a blocked user at any time.';
@override
String settingsPrivacyBlockUsersCount(Object len) {
return '$len contact(s)';
}
@override
String get settingsNotification => 'Notification';
@override
String get settingsNotifyTroubleshooting => 'Troubleshooting';
@override
String get settingsNotifyTroubleshootingDesc => 'Click here if you have problems receiving push notifications.';
@override
String get settingsNotifyTroubleshootingNoProblem => 'No problem detected';
@override
String get settingsNotifyTroubleshootingNoProblemDesc => 'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.';
@override
String get settingsHelp => 'Help';
@override
String get settingsHelpDiagnostics => 'Diagnostic protocol';
@override
String get settingsHelpSupport => 'Support Center';
@override
String get settingsHelpVersion => 'Version';
@override
String get settingsHelpLicenses => 'Licenses (Source-Code)';
@override
String get settingsHelpCredits => 'Licenses (Images)';
@override
String get settingsHelpLegal => 'Imprint, Terms & Privacy Policy';
@override
String get settingsAppearanceTheme => 'Theme';
@override
String get settingsAccountDeleteAccount => 'Delete account';
@override
String get settingsAccountDeleteModalTitle => 'Are you sure?';
@override
String get settingsAccountDeleteModalBody => 'Your account will be deleted. There is no change to restore it.';
@override
String get contactVerifyNumberTitle => 'Verify safety number';
@override
String get contactVerifyNumberMarkAsVerified => 'Mark as verified';
@override
String get contactVerifyNumberClearVerification => 'Clear verification';
@override
String contactVerifyNumberLongDesc(Object username) {
return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.';
}
@override
String get contactNickname => 'Nickname';
@override
String get contactNicknameNew => 'New nickname';
@override
String get deleteAllContactMessages => 'Delete all messages';
@override
String deleteAllContactMessagesBody(Object username) {
return 'This will remove all messages in your chat with $username. This will NOT delete the messages stored at ${username}s device!';
}
@override
String get contactBlock => 'Block';
@override
String contactBlockTitle(Object username) {
return 'Block $username';
}
@override
String get contactBlockBody => 'A blocked user will no longer be able to send you messages and their profile will be hidden from view. To unblock a user, simply navigate to Settings > Privacy > Blocked Users.';
@override
String get undo => 'Undo';
@override
String get redo => 'Redo';
@override
String get next => 'Next';
@override
String get close => 'Close';
@override
String get cancel => 'Cancel';
@override
String get ok => 'Ok';
@override
String get switchFrontAndBackCamera => 'Switch between front and back camera.';
@override
String get addTextItem => 'Text';
@override
String get protectAsARealTwonly => 'Send as real twonly!';
@override
String get addDrawing => 'Drawing';
@override
String get addEmoji => 'Emoji';
@override
String get toggleFlashLight => 'Toggle the flash light';
@override
String get toggleHighQuality => 'Toggle better resolution';
@override
String get userFound => 'User found';
@override
String get userFoundBody => 'Do you want to create a follow request?';
@override
String searchUsernameNotFoundLong(Object username) {
return '\"$username\" is not a twonly user. Please check the username and try again.';
}
@override
String get errorUnknown => 'An unexpected error has occurred. Please try again later.';
@override
String get errorBadRequest => 'The request could not be understood by the server due to malformed syntax. Please check your input and try again.';
@override
String get errorTooManyRequests => 'You have made too many requests in a short period. Please wait a moment before trying again.';
@override
String get errorInternalError => 'The server is currently not available. Please try again later.';
@override
String get errorInvalidInvitationCode => 'The invitation code you provided is invalid. Please check the code and try again.';
@override
String get errorUsernameAlreadyTaken => 'The username you want to use is already taken. Please choose a different username.';
@override
String get errorSignatureNotValid => 'The provided signature is not valid. Please check your credentials and try again.';
@override
String get errorUsernameNotFound => 'The username you entered does not exist. Please check the spelling or create a new account.';
@override
String get errorUsernameNotValid => 'The username you provided does not meet the required criteria. Please choose a valid username.';
@override
String get errorInvalidPublicKey => 'The public key you provided is invalid. Please check the key and try again.';
@override
String get errorSessionAlreadyAuthenticated => 'You are already logged in. Please log out if you want to log in with a different account.';
@override
String get errorSessionNotAuthenticated => 'Your session is not authenticated. Please log in to continue.';
@override
String get errorOnlyOneSessionAllowed => 'Only one active session is allowed per user. Please log out from other devices to continue.';
}

View file

@ -1,6 +1,6 @@
import 'dart:typed_data';
import 'package:json_annotation/json_annotation.dart';
import 'package:twonly/src/utils/json.dart';
import 'dart:convert';
part 'signal_identity.g.dart';
@JsonSerializable()
@ -16,3 +16,16 @@ class SignalIdentity {
_$SignalIdentityFromJson(json);
Map<String, dynamic> toJson() => _$SignalIdentityToJson(this);
}
class Uint8ListConverter implements JsonConverter<Uint8List, String> {
const Uint8ListConverter();
@override
Uint8List fromJson(String json) {
return base64Decode(json);
}
@override
String toJson(Uint8List object) {
return base64Encode(object);
}
}

View file

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart';
part 'userdata.g.dart';
@ -15,9 +16,12 @@ class UserData {
String? avatarSvg;
String? avatarJson;
int? avatarCounter;
// settings
int? defaultShowTime;
bool? useHighQuality;
List<String>? preSelectedEmojies;
ThemeMode? themeMode;
final int userId;

View file

@ -18,7 +18,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
..useHighQuality = json['useHighQuality'] as bool?
..preSelectedEmojies = (json['preSelectedEmojies'] as List<dynamic>?)
?.map((e) => e as String)
.toList();
.toList()
..themeMode = $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']);
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'username': instance.username,
@ -29,5 +30,12 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'defaultShowTime': instance.defaultShowTime,
'useHighQuality': instance.useHighQuality,
'preSelectedEmojies': instance.preSelectedEmojies,
'themeMode': _$ThemeModeEnumMap[instance.themeMode],
'userId': instance.userId,
};
const _$ThemeModeEnumMap = {
ThemeMode.system: 'system',
ThemeMode.light: 'light',
ThemeMode.dark: 'dark',
};

View file

@ -7,9 +7,9 @@ import 'package:mutex/mutex.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/providers/hive.dart';
import 'package:twonly/src/services/notification_service.dart';

View file

@ -1,8 +1,10 @@
import 'package:fixnum/fixnum.dart';
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
import 'package:twonly/src/model/protobuf/api/client_to_server.pb.dart'
as client;
import 'package:twonly/src/model/protobuf/api/client_to_server.pbserver.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart'
as server;
class Result<T, E> {
final T? value;

View file

@ -4,11 +4,11 @@ import 'package:drift/drift.dart';
import 'package:hive/hive.dart';
import 'package:logging/logging.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart';
import 'package:twonly/app.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/providers/hive.dart';

View file

@ -7,15 +7,17 @@ import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:logging/logging.dart';
import 'package:mutex/mutex.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart';
import 'package:twonly/app.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/proto/api/client_to_server.pb.dart' as client;
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
import 'package:twonly/src/proto/api/server_to_client.pbserver.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/protobuf/api/client_to_server.pb.dart'
as client;
import 'package:twonly/src/model/protobuf/api/client_to_server.pbserver.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart'
as server;
import 'package:twonly/src/model/protobuf/api/server_to_client.pbserver.dart';
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/providers/api/media.dart';
@ -115,9 +117,6 @@ Future<client.Response> handleDownloadData(DownloadData data) async {
return client.Response()..ok = ok;
}
// Uint8List? rawBytes =
// await SignalHelper.decryptBytes(downloadedBytes, fromUserId);
Message? msg = await twonlyDatabase.messagesDao
.getMessageByMessageId(messageId)
.getSingleOrNull();

View file

@ -5,14 +5,16 @@ import 'dart:io';
import 'dart:math';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:logging/logging.dart';
import 'package:mutex/mutex.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart';
import 'package:twonly/src/proto/api/client_to_server.pbserver.dart';
import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server;
import 'package:twonly/app.dart';
import 'package:twonly/src/model/protobuf/api/client_to_server.pbserver.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart'
as server;
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/api_utils.dart';
import 'package:twonly/src/providers/api/media.dart';
@ -220,7 +222,7 @@ class ApiProvider {
}
Future<bool> tryAuthenticateWithToken(int userId) async {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
String? apiAuthToken = await storage.read(key: "api_auth_token");
if (apiAuthToken != null) {
@ -296,7 +298,7 @@ class ApiProvider {
Uint8List apiAuthToken = result2.value.authtoken;
String apiAuthTokenB64 = base64Encode(apiAuthToken);
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
await storage.write(key: "api_auth_token", value: apiAuthTokenB64);
await tryAuthenticateWithToken(userData.userId);

View file

@ -1,12 +1,12 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
Future initMediaStorage() async {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
var containsEncryptionKey =
await storage.containsKey(key: 'hive_encryption_key');
if (!containsEncryptionKey) {
@ -23,7 +23,7 @@ Future initMediaStorage() async {
Future<Box> getMediaStorage() async {
try {
await initMediaStorage();
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
var encryptionKey =
base64Url.decode((await storage.read(key: 'hive_encryption_key'))!);

View file

@ -1,43 +1,30 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:twonly/src/services/settings_service.dart';
import 'package:twonly/src/utils/storage.dart';
class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
// Make SettingsService a private variable so it is not used directly.
final SettingsService _settingsService = SettingsService();
// Make ThemeMode a private variable so it is not updated directly without
// also persisting the changes with the SettingsService.
late ThemeMode _themeMode;
// Allow Widgets to read the user's preferred ThemeMode.
ThemeMode get themeMode => _themeMode;
/// Load the user's settings from the SettingsService. It may load from a
/// local database or the internet. The controller only knows it can load the
/// settings from the service.
Future<void> loadSettings() async {
_themeMode = await _settingsService.themeMode();
// Important! Inform listeners a change has occurred.
_themeMode = (await getUser())?.themeMode ?? ThemeMode.system;
notifyListeners();
}
/// Update and persist the ThemeMode based on the user's selection.
Future<void> updateThemeMode(ThemeMode? newThemeMode) async {
if (newThemeMode == null) return;
// Do not perform any work if new and old ThemeMode are identical
if (newThemeMode == _themeMode) return;
// Otherwise, store the new ThemeMode in memory
_themeMode = newThemeMode;
// Important! Inform listeners a change has occurred.
notifyListeners();
// Persist the changes to a local database or the internet using the
// SettingService.
await _settingsService.updateThemeMode(newThemeMode);
var user = await getUser();
if (user != null) {
user.themeMode = newThemeMode;
await updateUser(user);
}
}
}

View file

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

View file

@ -1,14 +1,14 @@
import 'dart:collection';
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/utils/misc.dart';
class ConnectSignedPreKeyStore extends SignedPreKeyStore {
// final store = HashMap<int, Uint8List>();
Future<HashMap<int, Uint8List>> getStore() async {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
final storeSerialized = await storage.read(key: "signed_pre_key_store");
var store = HashMap<int, Uint8List>();
if (storeSerialized == null) {
@ -22,7 +22,7 @@ class ConnectSignedPreKeyStore extends SignedPreKeyStore {
}
Future safeStore(HashMap<int, Uint8List> store) async {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
var storeHashMap = [];
for (final item in store.entries) {
storeHashMap.add([item.key, base64Encode(item.value)]);

View file

@ -1,8 +1,9 @@
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:logging/logging.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/app.dart';
import 'package:twonly/app.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
@ -14,7 +15,7 @@ import '../../firebase_options.dart';
Future initFCMAfterAuthenticated() async {
if (globalIsAppInBackground) return;
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
String? storedToken = await storage.read(key: "google_fcm");

View file

@ -14,9 +14,8 @@ import 'package:twonly/globals.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/json_models/message.dart' as my;
import 'package:twonly/src/model/json/message.dart' as my;
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/utils/misc.dart';
class PushUser {
String displayName;
@ -324,7 +323,7 @@ Future handlePushData(String pushDataJson) async {
}
Future<Map<int, PushUser>> getPushKeys(String storageKey) async {
var storage = getSecureStorage();
var storage = FlutterSecureStorage();
String? pushKeysJson = await storage.read(
key: storageKey,
iOptions: IOSOptions(
@ -344,7 +343,7 @@ Future<Map<int, PushUser>> getPushKeys(String storageKey) async {
}
Future setPushKeys(String storageKey, Map<int, PushUser> pushKeys) async {
var storage = getSecureStorage();
var storage = FlutterSecureStorage();
Map<String, dynamic> jsonToSend = {};
pushKeys.forEach((key, value) {
jsonToSend[key.toString()] = value.toJson();

View file

@ -1,17 +0,0 @@
import 'package:flutter/material.dart';
/// A service that stores and retrieves user settings.
///
/// By default, this class does not persist user settings. If you'd like to
/// persist the user settings locally, use the shared_preferences package. If
/// you'd like to store settings on a web server, use the http package.
class SettingsService {
/// Loads the User's preferred ThemeMode from local or remote storage.
Future<ThemeMode> themeMode() async => ThemeMode.system;
/// Persists the user's preferred ThemeMode to local or remote storage.
Future<void> updateThemeMode(ThemeMode theme) async {
// Use the shared_preferences package to persist settings locally or the
// http package to persist settings over the network.
}
}

View file

@ -1,16 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
import 'dart:convert';
import 'dart:typed_data';
class Uint8ListConverter implements JsonConverter<Uint8List, String> {
const Uint8ListConverter();
@override
Uint8List fromJson(String json) {
return base64Decode(json);
}
@override
String toJson(Uint8List object) {
return base64Encode(object);
}
}

View file

@ -9,12 +9,10 @@ import 'package:gal/gal.dart';
import 'package:local_auth/local_auth.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:pie_menu/pie_menu.dart';
import 'package:provider/provider.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/proto/api/error.pb.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
import 'package:twonly/src/localization/generated/app_localizations.dart';
extension ShortCutsExtension on BuildContext {
AppLocalizations get lang => AppLocalizations.of(this)!;
@ -45,15 +43,6 @@ Future<bool> deleteLogFile() async {
return false;
}
// Just a helper function to get the secure storage
FlutterSecureStorage getSecureStorage() {
// ignore: no_leading_underscores_for_local_identifiers
AndroidOptions _getAndroidOptions() => const AndroidOptions(
encryptedSharedPreferences: true,
);
return FlutterSecureStorage(aOptions: _getAndroidOptions());
}
Future<String?> saveImageToGallery(Uint8List imageBytes) async {
final hasAccess = await Gal.hasAccess();
if (!hasAccess) {
@ -181,30 +170,6 @@ Future<bool> isAllowedToDownload() async {
return true;
}
PieTheme getPieCanvasTheme(BuildContext context) {
return PieTheme(
brightness: Theme.of(context).brightness,
rightClickShowsMenu: true,
radius: 70,
buttonTheme: PieButtonTheme(
backgroundColor: Theme.of(context).colorScheme.tertiary,
iconColor: Theme.of(context).colorScheme.surfaceBright,
),
buttonThemeHovered: PieButtonTheme(
backgroundColor: Theme.of(context).colorScheme.primary,
iconColor: Theme.of(context).colorScheme.surfaceBright,
),
tooltipPadding: EdgeInsets.all(20),
overlayColor: const Color.fromARGB(41, 0, 0, 0),
// spacing: 0,
tooltipTextStyle: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w600,
),
);
}
void setupLogger() {
Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL;
Logger.root.onRecord.listen((record) async {
@ -215,3 +180,25 @@ void setupLogger() {
}
});
}
Uint8List intToBytes(int value) {
final byteData = ByteData(4);
byteData.setInt32(0, value, Endian.big);
return byteData.buffer.asUint8List();
}
int bytesToInt(Uint8List bytes) {
final byteData = ByteData.sublistView(bytes);
return byteData.getInt32(0, Endian.big);
}
List<Uint8List>? removeLastXBytes(Uint8List original, int count) {
if (original.length < count) {
return null;
}
final newList = Uint8List(original.length - count);
newList.setAll(0, original.sublist(0, original.length - count));
final lastXBytes = original.sublist(original.length - count);
return [newList, lastXBytes];
}

View file

@ -1,13 +1,14 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:logging/logging.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/json_models/signal_identity.dart';
import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart';
import 'package:twonly/src/signal/connect_signal_protocol_store.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/model/json/signal_identity.dart';
import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'package:twonly/src/providers/signal/connect_signal_protocol_store.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
@ -88,7 +89,7 @@ Future<ConnectSignalProtocolStore?> getSignalStore() async {
Future<SignalIdentity?> getSignalIdentity() async {
try {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
final signalIdentityJson = await storage.read(key: "signal_identity");
if (signalIdentityJson == null) {
return null;
@ -120,7 +121,7 @@ Future<List<PreKeyRecord>> getPreKeys() async {
}
Future createIfNotExistsSignalIdentity() async {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
final signalIdentity = await storage.read(key: "signal_identity");
if (signalIdentity != null) {
@ -172,81 +173,6 @@ Future<Fingerprint?> generateSessionFingerPrint(int target) async {
}
}
Uint8List intToBytes(int value) {
final byteData = ByteData(4);
byteData.setInt32(0, value, Endian.big);
return byteData.buffer.asUint8List();
}
int bytesToInt(Uint8List bytes) {
final byteData = ByteData.sublistView(bytes);
return byteData.getInt32(0, Endian.big);
}
List<Uint8List>? removeLastFourBytes(Uint8List original) {
if (original.length < 4) {
return null;
}
final newList = Uint8List(original.length - 4);
newList.setAll(0, original.sublist(0, original.length - 4));
final lastFourBytes = original.sublist(original.length - 4);
return [newList, lastFourBytes];
}
Future<Uint8List?> encryptBytes(Uint8List bytes, int target) async {
try {
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
SessionCipher session = SessionCipher.fromStore(
signalStore, SignalProtocolAddress(target.toString(), defaultDeviceId));
final ciphertext =
await session.encrypt(Uint8List.fromList(gzip.encode(bytes)));
var b = BytesBuilder();
b.add(ciphertext.serialize());
b.add(intToBytes(ciphertext.getType()));
return b.takeBytes();
} catch (e) {
Logger("utils/signal").shout(e.toString());
return null;
}
}
Future<Uint8List?> decryptBytes(Uint8List bytes, int target) async {
try {
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
SessionCipher session = SessionCipher.fromStore(
signalStore, SignalProtocolAddress(target.toString(), defaultDeviceId));
List<Uint8List>? msgs = removeLastFourBytes(bytes);
if (msgs == null) return null;
Uint8List body = msgs[0];
int type = bytesToInt(msgs[1]);
Uint8List plaintext;
Logger("utils/signal").info("got signal type: $type!");
if (type == CiphertextMessage.prekeyType) {
PreKeySignalMessage pre = PreKeySignalMessage(body);
plaintext = await session.decrypt(pre);
} else if (type == CiphertextMessage.whisperType) {
SignalMessage signalMsg = SignalMessage.fromSerialized(body);
plaintext = await session.decryptFromSignal(signalMsg);
} else {
Logger("utils/signal").shout("signal type is not known: $type!");
return null;
}
List<int>? plainBytes = gzip.decode(Uint8List.fromList(plaintext));
return Uint8List.fromList(plainBytes);
} catch (e) {
Logger("utils/signal").shout(e.toString());
return null;
}
}
Future<Uint8List?> encryptMessage(MessageJson msg, int target) async {
try {
ConnectSignalProtocolStore signalStore = (await getSignalStore())!;
@ -275,7 +201,7 @@ Future<MessageJson?> getDecryptedText(int source, Uint8List msg) async {
SessionCipher session = SessionCipher.fromStore(
signalStore, SignalProtocolAddress(source.toString(), defaultDeviceId));
List<Uint8List>? msgs = removeLastFourBytes(msg);
List<Uint8List>? msgs = removeLastXBytes(msg, 4);
if (msgs == null) return null;
Uint8List body = msgs[0];
int type = bytesToInt(msgs[1]);

View file

@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/model/json/userdata.dart';
Future<bool> isUserCreated() async {
UserData? user = await getUser();
@ -13,7 +13,7 @@ Future<bool> isUserCreated() async {
}
Future<UserData?> getUser() async {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
String? userJson = await storage.read(key: "userData");
if (userJson == null) {
return null;
@ -29,7 +29,7 @@ Future<UserData?> getUser() async {
}
Future updateUser(UserData userData) async {
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
storage.write(key: "userData", value: jsonEncode(userData));
}
@ -38,7 +38,7 @@ Future<bool> deleteLocalUserData() async {
if (appDir.existsSync()) {
appDir.deleteSync(recursive: true);
}
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
await storage.deleteAll();
return true;
}

View file

@ -1,21 +1,21 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:image_picker/image_picker.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/zoom_selector.dart';
import 'package:twonly/src/views/camera/components/zoom_selector.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/components/permissions_view.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/components/permissions_view.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera_to_share/share_image_editor_view.dart';
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
class CameraPreviewView extends StatefulWidget {
const CameraPreviewView({super.key, this.sendTo});
@ -91,12 +91,16 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
setState(() {
isZoomAble = false;
});
controller = CameraController(gCameras[sCameraId], ResolutionPreset.high,
enableAudio: false);
controller = CameraController(
gCameras[sCameraId],
ResolutionPreset.high,
enableAudio: false,
);
controller.initialize().then((_) async {
if (!mounted) {
return;
}
await controller.lockCaptureOrientation(DeviceOrientation.portraitUp);
controller.setFlashMode(isFlashOn ? FlashMode.always : FlashMode.off);
isZoomAble = await controller.getMinZoomLevel() !=
@ -146,12 +150,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
await File(picture.path).delete();
return imageBytes;
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error loading picture: $e'),
duration: Duration(seconds: 3),
),
);
if (context.mounted) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error loading picture: $e'),
duration: Duration(seconds: 3),
),
);
}
return null;
}
}
@ -174,12 +181,18 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
final XFile picture = await controller.takePicture();
imageBytes = loadAndDeletePictureFromFile(picture);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error taking picture: $e'),
duration: Duration(seconds: 3),
),
);
try {
if (context.mounted) {
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error taking picture: $e'),
duration: Duration(seconds: 3),
),
);
}
// ignore: empty_catches
} catch (e) {}
return;
}
} else {
@ -237,6 +250,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (!context.mounted) return true;
if (shoudReturn != null && shoudReturn) {
if (!context.mounted) return true;
// ignore: use_build_context_synchronously
Navigator.pop(context);
return true;
}
@ -409,8 +423,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
imageFile.readAsBytes())) {
return;
}
} else {
print('No image selected.');
}
setState(() {
galleryLoadedImageIsShown = false;

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/views/camera_to_share/camera_preview_view.dart';
import 'package:twonly/src/views/camera/camera_preview_view.dart';
class CameraSendToView extends StatefulWidget {
const CameraSendToView(this.sendTo, {super.key});

View file

@ -1,13 +1,13 @@
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/views/components/verified_shield.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/headline.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
class BestFriendsSelector extends StatelessWidget {
final List<Contact> users;

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hand_signature/signature.dart';
import 'package:twonly/src/components/image_editor/data/image_item.dart';
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
/// Layer class with some common properties
class Layer {

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
/// Main layer
class BackgroundLayer extends StatefulWidget {

View file

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:hand_signature/signature.dart';
import 'package:screenshot/screenshot.dart';
import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/utils/misc.dart';
class DrawLayer extends StatefulWidget {

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
/// Emoji layer
class EmojiLayer extends StatefulWidget {

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/components/image_editor/layers/filters/datetime_filter.dart';
import 'package:twonly/src/components/image_editor/layers/filters/image_filter.dart';
import 'package:twonly/src/components/image_editor/layers/filters/location_filter.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/image_filter.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/location_filter.dart';
/// Main layer
class FilterLayer extends StatefulWidget {

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:twonly/src/components/image_editor/layers/filter_layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
class DateTimeFilter extends StatelessWidget {
const DateTimeFilter({super.key, this.color = Colors.white});

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/components/image_editor/layers/filter_layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
class ImageFilter extends StatelessWidget {
const ImageFilter({super.key, required this.imagePath});

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/image_editor/layers/filter_layer.dart';
import 'package:twonly/src/components/image_editor/layers/filters/datetime_filter.dart';
import 'package:twonly/src/proto/api/server_to_client.pb.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filters/datetime_filter.dart';
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
/// Text layer
class TextLayer extends StatefulWidget {

View file

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/components/image_editor/layers/background_layer.dart';
import 'package:twonly/src/components/image_editor/layers/draw_layer.dart';
import 'package:twonly/src/components/image_editor/layers/emoji_layer.dart';
import 'package:twonly/src/components/image_editor/layers/filter_layer.dart';
import 'package:twonly/src/components/image_editor/layers/text_layer.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/background_layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/draw_layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/emoji_layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/filter_layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers/text_layer.dart';
/// View stacked layers (unbounded height, width)
class LayersViewer extends StatelessWidget {

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/components/image_editor/data/data.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/data/data.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
class Emojis extends StatefulWidget {
const Emojis({super.key});

View file

@ -1,21 +1,21 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/image_editor/action_button.dart';
import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/components/notification_badge.dart';
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/views/components/notification_badge.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera_to_share/share_image_view.dart';
import 'package:twonly/src/views/camera/share_image_view.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:twonly/src/components/image_editor/data/image_item.dart';
import 'package:twonly/src/components/image_editor/data/layer.dart';
import 'package:twonly/src/components/image_editor/layers_viewer.dart';
import 'package:twonly/src/components/image_editor/modules/all_emojis.dart';
import 'package:twonly/src/views/camera/image_editor/data/image_item.dart';
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart';
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
import 'package:screenshot/screenshot.dart';
List<Layer> layers = [];
@ -32,6 +32,7 @@ class ShareImageEditorView extends StatefulWidget {
}
class _ShareImageEditorView extends State<ShareImageEditorView> {
bool imageLoadedReady = false;
bool _imageSaved = false;
bool _imageSaving = false;
bool _isRealTwonly = false;
@ -246,6 +247,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Future<void> loadImage(Future<Uint8List?> imageFile) async {
Uint8List? imageBytes = await imageFile;
await currentImage.load(imageBytes);
if (!context.mounted) return;
layers.clear();
@ -256,7 +258,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
layers.add(FilterLayerData());
setState(() {});
setState(() {
imageLoadedReady = true;
});
}
@override
@ -268,7 +272,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
}
return Scaffold(
backgroundColor: Colors.white.withAlpha(0),
backgroundColor: imageLoadedReady ? null : Colors.white.withAlpha(0),
resizeToAvoidBottomInset: false,
body: Stack(
fit: StackFit.expand,

View file

@ -4,11 +4,11 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/best_friends_selector.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/views/camera/components/best_friends_selector.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/headline.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/verified_shield.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/providers/api/media.dart';

View file

@ -5,19 +5,19 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/components/better_text.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart';
import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/better_text.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/message_send_state_icon.dart';
import 'package:twonly/src/views/components/verified_shield.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/media_viewer_view.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/contact/contact_view.dart';

View file

@ -3,20 +3,20 @@ import 'dart:convert';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/connection_state.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/message_send_state_icon.dart';
import 'package:twonly/src/components/notification_badge.dart';
import 'package:twonly/src/components/user_context_menu.dart';
import 'package:twonly/src/views/components/connection_state.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/message_send_state_icon.dart';
import 'package:twonly/src/views/components/notification_badge.dart';
import 'package:twonly/src/views/components/user_context_menu.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/providers/connection_provider.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
import 'package:twonly/src/views/chats/media_viewer_view.dart';
import 'package:twonly/src/views/chats/start_new_chat.dart';

View file

@ -6,17 +6,17 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:lottie/lottie.dart';
import 'package:no_screenshot/no_screenshot.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/components/media_view_sizing.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/api/media.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
final _noScreenshot = NoScreenshot.instance;
@ -131,6 +131,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
if (isRealTwonly) {
if (!context.mounted) return;
// ignore: use_build_context_synchronously
bool isAuth = await authenticateUser(context.lang.mediaViewerAuthReason,
force: false);
if (!isAuth) {
@ -464,18 +465,16 @@ class _MediaViewerViewState extends State<MediaViewerView> {
},
),
Expanded(
child: Container(
child: TextField(
autofocus: true,
controller: textMessageController,
onEditingComplete: () {
setState(() {
showSendTextMessageInput = false;
showShortReactions = false;
});
},
decoration: inputTextMessageDeco(context),
),
child: TextField(
autofocus: true,
controller: textMessageController,
onEditingComplete: () {
setState(() {
showSendTextMessageInput = false;
showShortReactions = false;
});
},
decoration: inputTextMessageDeco(context),
),
),
IconButton(
@ -628,7 +627,7 @@ class EmojiReactionWidget extends StatefulWidget {
});
@override
_EmojiReactionWidgetState createState() => _EmojiReactionWidgetState();
State<EmojiReactionWidget> createState() => _EmojiReactionWidgetState();
}
class _EmojiReactionWidgetState extends State<EmojiReactionWidget> {

View file

@ -3,16 +3,16 @@ import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/headline.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/views/components/headline.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/providers/api/api.dart';
// ignore: library_prefixes
import 'package:twonly/src/utils/signal.dart' as SignalHelper;

View file

@ -4,9 +4,9 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:pie_menu/pie_menu.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/user_context_menu.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/user_context_menu.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/database/twonly_database.dart';
class FlameCounterWidget extends StatelessWidget {

View file

@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:logging/logging.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/model/json/userdata.dart';
class ContactAvatar extends StatelessWidget {
final Contact? contact;
@ -47,7 +48,7 @@ class ContactAvatar extends StatelessWidget {
child: SvgPicture.string(
avatarSvg,
errorBuilder: (context, error, stackTrace) {
print("Error: $error");
Logger("ui.avater").shout("$error");
return Container();
},
),

View file

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/database/tables/messages_table.dart';
import 'package:twonly/src/json_models/message.dart';
import 'package:twonly/src/model/json/message.dart';
import 'package:twonly/src/utils/misc.dart';
enum MessageSendState {

View file

@ -5,7 +5,7 @@ import 'package:pie_menu/pie_menu.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera_to_share/camera_send_to_view.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/chat_item_details_view.dart';
import 'package:twonly/src/views/contact/contact_verify_view.dart';
@ -89,3 +89,27 @@ class _UserContextMenuState extends State<UserContextMenu> {
);
}
}
PieTheme getPieCanvasTheme(BuildContext context) {
return PieTheme(
brightness: Theme.of(context).brightness,
rightClickShowsMenu: true,
radius: 70,
buttonTheme: PieButtonTheme(
backgroundColor: Theme.of(context).colorScheme.tertiary,
iconColor: Theme.of(context).colorScheme.surfaceBright,
),
buttonThemeHovered: PieButtonTheme(
backgroundColor: Theme.of(context).colorScheme.primary,
iconColor: Theme.of(context).colorScheme.surfaceBright,
),
tooltipPadding: EdgeInsets.all(20),
overlayColor: const Color.fromARGB(41, 0, 0, 0),
// spacing: 0,
tooltipTextStyle: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w600,
),
);
}

View file

@ -4,7 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/format_long_string.dart';
import 'package:twonly/src/views/components/format_long_string.dart';
import 'package:flutter/material.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';

View file

@ -1,11 +1,11 @@
import 'package:drift/drift.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/components/better_list_title.dart';
import 'package:twonly/src/components/flame.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/components/verified_shield.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/views/components/better_list_title.dart';
import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/views/components/verified_shield.dart';
import 'package:flutter/material.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';

View file

@ -1,9 +1,9 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:pie_menu/pie_menu.dart';
import 'package:twonly/src/views/components/user_context_menu.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'camera_to_share/camera_preview_view.dart';
import 'camera/camera_preview_view.dart';
import 'chats/chat_list_view.dart';
import 'package:flutter/material.dart';

View file

@ -1,11 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:logging/logging.dart';
import 'package:twonly/globals.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/signal.dart';
@ -30,7 +31,7 @@ class _RegisterViewState extends State<RegisterView> {
_isTryingToRegister = true;
});
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
await createIfNotExistsSignalIdentity();
@ -57,6 +58,7 @@ class _RegisterViewState extends State<RegisterView> {
}
if (context.mounted) {
// ignore: use_build_context_synchronously
showAlertDialog(context, "Oh no!", errorCodeToText(context, res.error));
}
}

View file

@ -1,6 +1,6 @@
import 'package:restart_app/restart_app.dart';
import 'package:flutter/material.dart';
import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:twonly/src/components/radio_button.dart';
import 'package:twonly/src/views/components/radio_button.dart';
import 'package:twonly/src/providers/settings_change_provider.dart';
import 'package:twonly/src/utils/misc.dart';

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:twonly/src/components/animate_icon.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';

View file

@ -2,8 +2,9 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/alert_dialog.dart';
import 'package:twonly/src/views/components/alert_dialog.dart';
import 'package:twonly/src/services/fcm_service.dart';
import 'package:twonly/src/services/notification_service.dart';
import 'package:twonly/src/utils/misc.dart';
@ -25,7 +26,7 @@ class NotificationView extends StatelessWidget {
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc),
onTap: () async {
await initFCMAfterAuthenticated();
final storage = getSecureStorage();
final storage = FlutterSecureStorage();
String? storedToken = await storage.read(key: "google_fcm");
await setupNotificationWithUsers(force: true);
if (!context.mounted) return;

View file

@ -1,7 +1,7 @@
import 'package:drift/drift.dart' hide Column;
import 'package:flutter/material.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/components/initialsavatar.dart';
import 'package:twonly/src/views/components/initialsavatar.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart';

View file

@ -3,7 +3,7 @@ import 'package:avatar_maker/avatar_maker.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:provider/provider.dart';
import 'package:twonly/src/json_models/userdata.dart';
import 'package:twonly/src/model/json/userdata.dart';
import 'package:twonly/src/providers/api/api.dart';
import 'package:twonly/src/providers/settings_change_provider.dart';
import 'package:twonly/src/utils/misc.dart';

Some files were not shown because too many files have changed in this diff Show more