mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:38:41 +00:00
Merge pull request #301 from twonlyapp/dev
- Adds crash reports (optional). Please consider enabling this under Settings > Help > “Share errors and crashes with us.” - Fixes bug when saving images to the gallery - Multiple layout issues fixed - Multiple bug fixes
This commit is contained in:
commit
0260d552bf
35 changed files with 513 additions and 243 deletions
|
|
@ -1,5 +1,12 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.0.67
|
||||||
|
|
||||||
|
- Adds crash reports (optional). Please consider enabling this under Settings > Help > “Share errors and crashes with us.”
|
||||||
|
- Fixes bug when saving images to the gallery
|
||||||
|
- Multiple layout issues fixed
|
||||||
|
- Multiple bug fixes
|
||||||
|
|
||||||
## 0.0.62
|
## 0.0.62
|
||||||
|
|
||||||
- Support for groups with multiple administrators
|
- Support for groups with multiple administrators
|
||||||
|
|
@ -16,7 +23,6 @@
|
||||||
- Improved reliability of client-to-client messaging
|
- Improved reliability of client-to-client messaging
|
||||||
- Multiple bug fixes
|
- Multiple bug fixes
|
||||||
|
|
||||||
|
|
||||||
## 0.0.61
|
## 0.0.61
|
||||||
|
|
||||||
- Improving image editor when changing colors
|
- Improving image editor when changing colors
|
||||||
|
|
|
||||||
|
|
@ -191,6 +191,8 @@ PODS:
|
||||||
- "no_screenshot (0.0.1+4)":
|
- "no_screenshot (0.0.1+4)":
|
||||||
- Flutter
|
- Flutter
|
||||||
- ScreenProtectorKit (~> 1.3.1)
|
- ScreenProtectorKit (~> 1.3.1)
|
||||||
|
- objective_c (0.0.1):
|
||||||
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
|
|
@ -208,6 +210,11 @@ PODS:
|
||||||
- SDWebImageWebPCoder (0.14.6):
|
- SDWebImageWebPCoder (0.14.6):
|
||||||
- libwebp (~> 1.0)
|
- libwebp (~> 1.0)
|
||||||
- SDWebImage/Core (~> 5.17)
|
- SDWebImage/Core (~> 5.17)
|
||||||
|
- Sentry/HybridSDK (8.56.2)
|
||||||
|
- sentry_flutter (9.8.0):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- Sentry/HybridSDK (= 8.56.2)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
|
|
@ -275,10 +282,12 @@ DEPENDENCIES:
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||||
- no_screenshot (from `.symlinks/plugins/no_screenshot/ios`)
|
- no_screenshot (from `.symlinks/plugins/no_screenshot/ios`)
|
||||||
|
- objective_c (from `.symlinks/plugins/objective_c/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- restart_app (from `.symlinks/plugins/restart_app/ios`)
|
- restart_app (from `.symlinks/plugins/restart_app/ios`)
|
||||||
|
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
|
|
@ -306,6 +315,7 @@ SPEC REPOS:
|
||||||
- ScreenProtectorKit
|
- ScreenProtectorKit
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
|
- Sentry
|
||||||
- sqlite3
|
- sqlite3
|
||||||
- SwiftProtobuf
|
- SwiftProtobuf
|
||||||
|
|
||||||
|
|
@ -352,6 +362,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||||
no_screenshot:
|
no_screenshot:
|
||||||
:path: ".symlinks/plugins/no_screenshot/ios"
|
:path: ".symlinks/plugins/no_screenshot/ios"
|
||||||
|
objective_c:
|
||||||
|
:path: ".symlinks/plugins/objective_c/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
|
|
@ -360,6 +372,8 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
restart_app:
|
restart_app:
|
||||||
:path: ".symlinks/plugins/restart_app/ios"
|
:path: ".symlinks/plugins/restart_app/ios"
|
||||||
|
sentry_flutter:
|
||||||
|
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
|
|
@ -408,6 +422,7 @@ SPEC CHECKSUMS:
|
||||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
no_screenshot: 6d183496405a3ab709a67a54e5cd0f639e94729e
|
no_screenshot: 6d183496405a3ab709a67a54e5cd0f639e94729e
|
||||||
|
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
|
|
@ -416,6 +431,8 @@ SPEC CHECKSUMS:
|
||||||
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
|
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
|
||||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||||
|
Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7
|
||||||
|
sentry_flutter: 4c33648b7e83310aa1fdb1b10c5491027d9643f0
|
||||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,4 @@ void Function(SubscriptionPlan plan) globalCallbackUpdatePlan =
|
||||||
Map<String, VoidCallback> globalUserDataChangedCallBack = {};
|
Map<String, VoidCallback> globalUserDataChangedCallBack = {};
|
||||||
|
|
||||||
bool globalIsAppInBackground = true;
|
bool globalIsAppInBackground = true;
|
||||||
|
bool globalAllowErrorTrackingViaSentry = false;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
import 'package:twonly/app.dart';
|
import 'package:twonly/app.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
@ -69,13 +70,34 @@ void main() async {
|
||||||
unawaited(createPushAvatars());
|
unawaited(createPushAvatars());
|
||||||
await twonlyDB.messagesDao.purgeMessageTable();
|
await twonlyDB.messagesDao.purgeMessageTable();
|
||||||
|
|
||||||
|
final providers = [
|
||||||
|
ChangeNotifierProvider(create: (_) => settingsController),
|
||||||
|
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
||||||
|
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
if (user.allowErrorTrackingViaSentry) {
|
||||||
|
globalAllowErrorTrackingViaSentry = true;
|
||||||
|
return SentryFlutter.init(
|
||||||
|
(options) => options
|
||||||
|
..dsn =
|
||||||
|
'https://6b24a012c85144c9b522440a1d17d01c@glitchtip.twonly.eu/4'
|
||||||
|
..tracesSampleRate = 0.01
|
||||||
|
..enableAutoSessionTracking = false,
|
||||||
|
appRunner: () => runApp(
|
||||||
|
MultiProvider(
|
||||||
|
providers: providers,
|
||||||
|
child: const App(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: providers,
|
||||||
ChangeNotifierProvider(create: (_) => settingsController),
|
|
||||||
ChangeNotifierProvider(create: (_) => CustomChangeProvider()),
|
|
||||||
ChangeNotifierProvider(create: (_) => ImageEditorProvider()),
|
|
||||||
],
|
|
||||||
child: const App(),
|
child: const App(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -322,11 +322,11 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
flameCounter += 1;
|
flameCounter += 1;
|
||||||
lastFlameCounterChange = Value(timestamp);
|
lastFlameCounterChange = Value(timestamp);
|
||||||
// Overwrite max flame counter either the current is bigger or the th max flame counter is older then 4 days
|
// Overwrite max flame counter either the current is bigger or the th max flame counter is older then 4 days
|
||||||
if ((flameCounter + 1) >= maxFlameCounter ||
|
if (flameCounter >= maxFlameCounter ||
|
||||||
maxFlameCounterFrom == null ||
|
maxFlameCounterFrom == null ||
|
||||||
maxFlameCounterFrom
|
maxFlameCounterFrom
|
||||||
.isBefore(DateTime.now().subtract(const Duration(days: 5)))) {
|
.isBefore(DateTime.now().subtract(const Duration(days: 5)))) {
|
||||||
maxFlameCounter = flameCounter + 1;
|
maxFlameCounter = flameCounter;
|
||||||
maxFlameCounterFrom = DateTime.now();
|
maxFlameCounterFrom = DateTime.now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ class SignalDao extends DatabaseAccessor<TwonlyDB> with _$SignalDaoMixin {
|
||||||
tbl.preKeyId.equals(preKey.preKeyId),
|
tbl.preKeyId.equals(preKey.preKeyId),
|
||||||
))
|
))
|
||||||
.go();
|
.go();
|
||||||
Log.info('[PREKEY] Using prekey ${preKey.preKeyId} for $contactId');
|
|
||||||
return preKey;
|
return preKey;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -68,7 +67,6 @@ class SignalDao extends DatabaseAccessor<TwonlyDB> with _$SignalDaoMixin {
|
||||||
List<SignalContactPreKeysCompanion> preKeys,
|
List<SignalContactPreKeysCompanion> preKeys,
|
||||||
) async {
|
) async {
|
||||||
for (final preKey in preKeys) {
|
for (final preKey in preKeys) {
|
||||||
Log.info('[PREKEY] Inserting others ${preKey.preKeyId}');
|
|
||||||
try {
|
try {
|
||||||
await into(signalContactPreKeys).insert(preKey);
|
await into(signalContactPreKeys).insert(preKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,12 @@ class ConnectPreKeyStore extends PreKeyStore {
|
||||||
'[PREKEY] No such preKey record! - $preKeyId',
|
'[PREKEY] No such preKey record! - $preKeyId',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Log.info('[PREKEY] Contact used my preKey $preKeyId');
|
|
||||||
final preKey = preKeyRecord.first.preKey;
|
final preKey = preKeyRecord.first.preKey;
|
||||||
return PreKeyRecord.fromBuffer(preKey);
|
return PreKeyRecord.fromBuffer(preKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> removePreKey(int preKeyId) async {
|
Future<void> removePreKey(int preKeyId) async {
|
||||||
Log.info('[PREKEY] Removing $preKeyId from my own storage.');
|
|
||||||
await (twonlyDB.delete(twonlyDB.signalPreKeyStores)
|
await (twonlyDB.delete(twonlyDB.signalPreKeyStores)
|
||||||
..where((tbl) => tbl.preKeyId.equals(preKeyId)))
|
..where((tbl) => tbl.preKeyId.equals(preKeyId)))
|
||||||
.go();
|
.go();
|
||||||
|
|
@ -43,7 +41,6 @@ class ConnectPreKeyStore extends PreKeyStore {
|
||||||
preKey: Value(record.serialize()),
|
preKey: Value(record.serialize()),
|
||||||
);
|
);
|
||||||
|
|
||||||
Log.info('[PREKEY] Storing $preKeyId from my own storage.');
|
|
||||||
try {
|
try {
|
||||||
await twonlyDB.into(twonlyDB.signalPreKeyStores).insert(preKeyCompanion);
|
await twonlyDB.into(twonlyDB.signalPreKeyStores).insert(preKeyCompanion);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -818,5 +818,9 @@
|
||||||
"deleteChatAfterAMonth": "einem Monat.",
|
"deleteChatAfterAMonth": "einem Monat.",
|
||||||
"deleteChatAfterAYear": "einem Jahr.",
|
"deleteChatAfterAYear": "einem Jahr.",
|
||||||
"yourTwonlyScore": "Dein twonly-Score",
|
"yourTwonlyScore": "Dein twonly-Score",
|
||||||
"registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal."
|
"registrationClosed": "Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.",
|
||||||
|
"dialogAskDeleteMediaFilePopTitle": "Bist du sicher, dass du dein Meisterwerk löschen möchtest?",
|
||||||
|
"dialogAskDeleteMediaFilePopDelete": "Löschen",
|
||||||
|
"allowErrorTracking": "Fehler und Crashes mit uns teilen",
|
||||||
|
"allowErrorTrackingSubtitle": "Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen."
|
||||||
}
|
}
|
||||||
|
|
@ -596,5 +596,9 @@
|
||||||
"deleteChatAfterAMonth": "one month.",
|
"deleteChatAfterAMonth": "one month.",
|
||||||
"deleteChatAfterAYear": "one year.",
|
"deleteChatAfterAYear": "one year.",
|
||||||
"yourTwonlyScore": "Your twonly-Score",
|
"yourTwonlyScore": "Your twonly-Score",
|
||||||
"registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days."
|
"registrationClosed": "Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.",
|
||||||
|
"dialogAskDeleteMediaFilePopTitle": "Are you sure you want to delete your masterpiece?",
|
||||||
|
"dialogAskDeleteMediaFilePopDelete": "Delete",
|
||||||
|
"allowErrorTracking": "Share errors and crashes with us",
|
||||||
|
"allowErrorTrackingSubtitle": "If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded."
|
||||||
}
|
}
|
||||||
|
|
@ -2683,6 +2683,30 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.'**
|
/// **'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.'**
|
||||||
String get registrationClosed;
|
String get registrationClosed;
|
||||||
|
|
||||||
|
/// No description provided for @dialogAskDeleteMediaFilePopTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Are you sure you want to delete your masterpiece?'**
|
||||||
|
String get dialogAskDeleteMediaFilePopTitle;
|
||||||
|
|
||||||
|
/// No description provided for @dialogAskDeleteMediaFilePopDelete.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Delete'**
|
||||||
|
String get dialogAskDeleteMediaFilePopDelete;
|
||||||
|
|
||||||
|
/// No description provided for @allowErrorTracking.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Share errors and crashes with us'**
|
||||||
|
String get allowErrorTracking;
|
||||||
|
|
||||||
|
/// No description provided for @allowErrorTrackingSubtitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.'**
|
||||||
|
String get allowErrorTrackingSubtitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1481,4 +1481,18 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get registrationClosed =>
|
String get registrationClosed =>
|
||||||
'Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.';
|
'Aufgrund des aktuell sehr hohen Aufkommens haben wir die Registrierung vorübergehend deaktiviert, damit der Dienst zuverlässig bleibt. Bitte versuche es in ein paar Tagen noch einmal.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialogAskDeleteMediaFilePopTitle =>
|
||||||
|
'Bist du sicher, dass du dein Meisterwerk löschen möchtest?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialogAskDeleteMediaFilePopDelete => 'Löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTracking => 'Fehler und Crashes mit uns teilen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTrackingSubtitle =>
|
||||||
|
'Wenn twonly abstürzt oder Fehler auftreten, werden diese automatisch an unsere selbst gehostete Glitchtip-Instanz gemeldet. Persönliche Daten wie Nachrichten oder Bilder werden niemals hochgeladen.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1471,4 +1471,18 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get registrationClosed =>
|
String get registrationClosed =>
|
||||||
'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.';
|
'Due to the current high volume of registrations, we have temporarily disabled registration to ensure that the service remains reliable. Please try again in a few days.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialogAskDeleteMediaFilePopTitle =>
|
||||||
|
'Are you sure you want to delete your masterpiece?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get dialogAskDeleteMediaFilePopDelete => 'Delete';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTracking => 'Share errors and crashes with us';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get allowErrorTrackingSubtitle =>
|
||||||
|
'If twonly crashes or errors occur, these are automatically reported to our self-hosted Glitchtip instance. Personal data such as messages or images are never uploaded.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,6 @@ class UserData {
|
||||||
|
|
||||||
int? defaultShowTime;
|
int? defaultShowTime;
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
|
||||||
bool useHighQuality = true;
|
|
||||||
|
|
||||||
@JsonKey(defaultValue: false)
|
@JsonKey(defaultValue: false)
|
||||||
bool requestedAudioPermission = false;
|
bool requestedAudioPermission = false;
|
||||||
|
|
||||||
|
|
@ -73,6 +70,9 @@ class UserData {
|
||||||
|
|
||||||
DateTime? signalLastSignedPreKeyUpdated;
|
DateTime? signalLastSignedPreKeyUpdated;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool allowErrorTrackingViaSentry = false;
|
||||||
|
|
||||||
// -- Custom DATA --
|
// -- Custom DATA --
|
||||||
|
|
||||||
@JsonKey(defaultValue: 100_000)
|
@JsonKey(defaultValue: 100_000)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
$enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
$enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
|
||||||
ThemeMode.system
|
ThemeMode.system
|
||||||
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
|
||||||
..useHighQuality = json['useHighQuality'] as bool? ?? true
|
|
||||||
..requestedAudioPermission =
|
..requestedAudioPermission =
|
||||||
json['requestedAudioPermission'] as bool? ?? false
|
json['requestedAudioPermission'] as bool? ?? false
|
||||||
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
|
||||||
|
|
@ -50,6 +49,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
json['signalLastSignedPreKeyUpdated'] == null
|
json['signalLastSignedPreKeyUpdated'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
: DateTime.parse(json['signalLastSignedPreKeyUpdated'] as String)
|
||||||
|
..allowErrorTrackingViaSentry =
|
||||||
|
json['allowErrorTrackingViaSentry'] as bool? ?? false
|
||||||
..currentPreKeyIndexStart =
|
..currentPreKeyIndexStart =
|
||||||
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
(json['currentPreKeyIndexStart'] as num?)?.toInt() ?? 100000
|
||||||
..currentSignedPreKeyIndexStart =
|
..currentSignedPreKeyIndexStart =
|
||||||
|
|
@ -84,7 +85,6 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'todaysImageCounter': instance.todaysImageCounter,
|
'todaysImageCounter': instance.todaysImageCounter,
|
||||||
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
|
||||||
'defaultShowTime': instance.defaultShowTime,
|
'defaultShowTime': instance.defaultShowTime,
|
||||||
'useHighQuality': instance.useHighQuality,
|
|
||||||
'requestedAudioPermission': instance.requestedAudioPermission,
|
'requestedAudioPermission': instance.requestedAudioPermission,
|
||||||
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
'showFeedbackShortcut': instance.showFeedbackShortcut,
|
||||||
'preSelectedEmojies': instance.preSelectedEmojies,
|
'preSelectedEmojies': instance.preSelectedEmojies,
|
||||||
|
|
@ -96,6 +96,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'myBestFriendGroupId': instance.myBestFriendGroupId,
|
'myBestFriendGroupId': instance.myBestFriendGroupId,
|
||||||
'signalLastSignedPreKeyUpdated':
|
'signalLastSignedPreKeyUpdated':
|
||||||
instance.signalLastSignedPreKeyUpdated?.toIso8601String(),
|
instance.signalLastSignedPreKeyUpdated?.toIso8601String(),
|
||||||
|
'allowErrorTrackingViaSentry': instance.allowErrorTrackingViaSentry,
|
||||||
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
'currentPreKeyIndexStart': instance.currentPreKeyIndexStart,
|
||||||
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
'currentSignedPreKeyIndexStart': instance.currentSignedPreKeyIndexStart,
|
||||||
'lastChangeLogHash': instance.lastChangeLogHash,
|
'lastChangeLogHash': instance.lastChangeLogHash,
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ Future<void> requestMediaReupload(String mediaId) async {
|
||||||
Future<void> handleEncryptedFile(String mediaId) async {
|
Future<void> handleEncryptedFile(String mediaId) async {
|
||||||
final mediaService = await MediaFileService.fromMediaId(mediaId);
|
final mediaService = await MediaFileService.fromMediaId(mediaId);
|
||||||
if (mediaService == null) {
|
if (mediaService == null) {
|
||||||
Log.error('Media file $mediaId not found in database.');
|
Log.error('Media file not found in database.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,7 +263,7 @@ Future<void> handleEncryptedFile(String mediaId) async {
|
||||||
try {
|
try {
|
||||||
encryptedBytes = await mediaService.encryptedPath.readAsBytes();
|
encryptedBytes = await mediaService.encryptedPath.readAsBytes();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Could not read encrypted media file: $mediaId. $e');
|
Log.error('Could not read encrypted media file: $e');
|
||||||
await requestMediaReupload(mediaId);
|
await requestMediaReupload(mediaId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,14 +44,14 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
if (receipt == null) {
|
if (receipt == null) {
|
||||||
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
receipt = await twonlyDB.receiptsDao.getReceiptById(receiptId!);
|
||||||
if (receipt == null) {
|
if (receipt == null) {
|
||||||
Log.error('Receipt $receiptId not found.');
|
Log.error('Receipt not found.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
receiptId = receipt.receiptId;
|
receiptId = receipt.receiptId;
|
||||||
|
|
||||||
if (!onlyReturnEncryptedData && receipt.ackByServerAt != null) {
|
if (!onlyReturnEncryptedData && receipt.ackByServerAt != null) {
|
||||||
Log.error('$receiptId message already uploaded!');
|
Log.error('message already uploaded!');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,15 +231,14 @@ class MediaFileService {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (originalPath.existsSync()) {
|
if (originalPath.existsSync() && !tempPath.existsSync()) {
|
||||||
await originalPath.copy(tempPath.path);
|
|
||||||
await compressMedia();
|
await compressMedia();
|
||||||
}
|
}
|
||||||
if (tempPath.existsSync()) {
|
if (tempPath.existsSync()) {
|
||||||
await tempPath.copy(storedPath.path);
|
await tempPath.copy(storedPath.path);
|
||||||
} else {
|
} else {
|
||||||
Log.error(
|
Log.error(
|
||||||
'Could not store image neither tempPath nor originalPath exists.',
|
'Could not store image neither as tempPath does not exists.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
unawaited(createThumbnail());
|
unawaited(createThumbnail());
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
|
|
||||||
void initLogger() {
|
void initLogger() {
|
||||||
// Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL;
|
// Logger.root.level = kReleaseMode ? Level.INFO : Level.ALL;
|
||||||
|
|
@ -20,6 +22,13 @@ void initLogger() {
|
||||||
|
|
||||||
class Log {
|
class Log {
|
||||||
static void error(Object? message, [Object? error, StackTrace? stackTrace]) {
|
static void error(Object? message, [Object? error, StackTrace? stackTrace]) {
|
||||||
|
if (globalAllowErrorTrackingViaSentry) {
|
||||||
|
try {
|
||||||
|
throw Exception(message);
|
||||||
|
} catch (exception, stackTrace) {
|
||||||
|
Sentry.captureException(exception, stackTrace: stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
Logger(_getCallerSourceCodeFilename()).shout(message, error, stackTrace);
|
Logger(_getCallerSourceCodeFilename()).shout(message, error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +54,15 @@ Future<String> loadLogFile() async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> readLast1000Lines() async {
|
||||||
|
final dir = await getApplicationSupportDirectory();
|
||||||
|
final file = File('${dir.path}/app.log');
|
||||||
|
if (!file.existsSync()) return '';
|
||||||
|
final all = await file.readAsLines();
|
||||||
|
final start = all.length > 1000 ? all.length - 1000 : 0;
|
||||||
|
return all.sublist(start).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _writeLogToFile(LogRecord record) async {
|
Future<void> _writeLogToFile(LogRecord record) async {
|
||||||
final directory = await getApplicationSupportDirectory();
|
final directory = await getApplicationSupportDirectory();
|
||||||
final logFile = File('${directory.path}/app.log');
|
final logFile = File('${directory.path}/app.log');
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ Future<String?> saveImageToGallery(Uint8List imageBytes) async {
|
||||||
await Gal.putImageBytes(jpgImages);
|
await Gal.putImageBytes(jpgImages);
|
||||||
return null;
|
return null;
|
||||||
} on GalException catch (e) {
|
} on GalException catch (e) {
|
||||||
|
Log.error(e);
|
||||||
return e.type.message;
|
return e.type.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +53,7 @@ Future<String?> saveVideoToGallery(String videoPath) async {
|
||||||
await Gal.putVideo(videoPath);
|
await Gal.putVideo(videoPath);
|
||||||
return null;
|
return null;
|
||||||
} on GalException catch (e) {
|
} on GalException catch (e) {
|
||||||
|
Log.error(e);
|
||||||
return e.type.message;
|
return e.type.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ Future<UserData?> updateUserdata(
|
||||||
final userData = await updateProtection.protect<UserData?>(() async {
|
final userData = await updateProtection.protect<UserData?>(() async {
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
|
if (user.defaultShowTime == 999999) {
|
||||||
|
// This was the old version for infinity -> change it to null
|
||||||
|
user.defaultShowTime = null;
|
||||||
|
}
|
||||||
final updated = updateUser(user);
|
final updated = updateUser(user);
|
||||||
await const FlutterSecureStorage()
|
await const FlutterSecureStorage()
|
||||||
.write(key: SecureStorageKeys.userData, value: jsonEncode(updated));
|
.write(key: SecureStorageKeys.userData, value: jsonEncode(updated));
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ class HomeViewCameraPreview extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
|
requiredHeight: 90,
|
||||||
|
bottomNavigation: Container(),
|
||||||
child: Screenshot(
|
child: Screenshot(
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
|
|
@ -58,6 +60,8 @@ class SendToCameraPreview extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: MediaViewSizing(
|
child: MediaViewSizing(
|
||||||
|
requiredHeight: 90,
|
||||||
|
bottomNavigation: Container(),
|
||||||
child: Screenshot(
|
child: Screenshot(
|
||||||
controller: screenshotController,
|
controller: screenshotController,
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class SaveToGalleryButton extends StatefulWidget {
|
class SaveToGalleryButton extends StatefulWidget {
|
||||||
|
|
@ -54,22 +55,24 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
|
||||||
|
|
||||||
final storedMediaPath = widget.mediaService.storedPath;
|
final storedMediaPath = widget.mediaService.storedPath;
|
||||||
|
|
||||||
final storeToGallery = gUser.storeMediaFilesInGallery;
|
|
||||||
|
|
||||||
await widget.mediaService.storeMediaFile();
|
await widget.mediaService.storeMediaFile();
|
||||||
|
|
||||||
if (storeToGallery) {
|
if (gUser.storeMediaFilesInGallery) {
|
||||||
res = await saveVideoToGallery(storedMediaPath.path);
|
if (widget.mediaService.mediaFile.type == MediaType.video) {
|
||||||
|
res = await saveVideoToGallery(storedMediaPath.path);
|
||||||
|
} else {
|
||||||
|
res = await saveImageToGallery(
|
||||||
|
storedMediaPath.readAsBytesSync(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await widget.mediaService.compressMedia();
|
|
||||||
await widget.mediaService.createThumbnail();
|
|
||||||
|
|
||||||
if (res == null) {
|
if (res == null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_imageSaved = true;
|
_imageSaved = true;
|
||||||
});
|
});
|
||||||
} else if (mounted && context.mounted) {
|
} else if (mounted && context.mounted) {
|
||||||
|
Log.error('Could not store media file in the gallery.');
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(res),
|
content: Text(res),
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
|
|
||||||
FlutterVolumeController.addListener(
|
FlutterVolumeController.addListener(
|
||||||
(volume) async {
|
(volume) async {
|
||||||
|
if (!widget.isVisible) {
|
||||||
|
await deInitVolumeControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (startedVolume == null) {
|
if (startedVolume == null) {
|
||||||
startedVolume = volume;
|
startedVolume = volume;
|
||||||
return;
|
return;
|
||||||
|
|
@ -221,7 +225,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
}
|
}
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
androidVolumeDownSub = FlutterAndroidVolumeKeydown.stream.listen((event) {
|
androidVolumeDownSub = FlutterAndroidVolumeKeydown.stream.listen((event) {
|
||||||
takePicture();
|
if (widget.isVisible) {
|
||||||
|
takePicture();
|
||||||
|
} else {
|
||||||
|
deInitVolumeControl();
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -581,6 +590,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
return MediaViewSizing(
|
return MediaViewSizing(
|
||||||
|
requiredHeight: 90,
|
||||||
|
bottomNavigation: Container(),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onPanStart: (details) async {
|
onPanStart: (details) async {
|
||||||
if (isFront) {
|
if (isFront) {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ class _FilterLayerState extends State<FilterLayer> {
|
||||||
List<Widget> pages = [
|
List<Widget> pages = [
|
||||||
const FilterSkeleton(),
|
const FilterSkeleton(),
|
||||||
const DateTimeFilter(),
|
const DateTimeFilter(),
|
||||||
const LocationFilter(),
|
// const LocationFilter(),
|
||||||
const FilterSkeleton(),
|
const FilterSkeleton(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,27 @@ class _TextViewState extends State<TextLayer> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> onEditionComplete() async {
|
||||||
|
Future.delayed(const Duration(milliseconds: 10), () async {
|
||||||
|
setState(() {
|
||||||
|
widget.layerData.isDeleted = textController.text == '';
|
||||||
|
widget.layerData.isEditing = false;
|
||||||
|
widget.layerData.text = textController.text;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
await context
|
||||||
|
.read<ImageEditorProvider>()
|
||||||
|
.updateSomeTextViewIsAlreadyEditing(false);
|
||||||
|
if (widget.onUpdate != null) {
|
||||||
|
widget.onUpdate!();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
double maxBottomInset = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (widget.layerData.isDeleted) return Container();
|
if (widget.layerData.isDeleted) return Container();
|
||||||
|
|
@ -69,6 +90,16 @@ class _TextViewState extends State<TextLayer> {
|
||||||
final bottom = MediaQuery.of(context).viewInsets.bottom +
|
final bottom = MediaQuery.of(context).viewInsets.bottom +
|
||||||
MediaQuery.of(context).viewPadding.bottom;
|
MediaQuery.of(context).viewPadding.bottom;
|
||||||
|
|
||||||
|
if (maxBottomInset > bottom) {
|
||||||
|
maxBottomInset = 0;
|
||||||
|
if (widget.layerData.isEditing) {
|
||||||
|
widget.layerData.isEditing = false;
|
||||||
|
onEditionComplete();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
maxBottomInset = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
if (widget.layerData.isEditing) {
|
if (widget.layerData.isEditing) {
|
||||||
return Positioned(
|
return Positioned(
|
||||||
bottom: bottom - localBottom,
|
bottom: bottom - localBottom,
|
||||||
|
|
@ -83,20 +114,7 @@ class _TextViewState extends State<TextLayer> {
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
onEditingComplete: () async {
|
onEditingComplete: onEditionComplete,
|
||||||
setState(() {
|
|
||||||
widget.layerData.isDeleted = textController.text == '';
|
|
||||||
widget.layerData.isEditing = false;
|
|
||||||
widget.layerData.text = textController.text;
|
|
||||||
});
|
|
||||||
|
|
||||||
await context
|
|
||||||
.read<ImageEditorProvider>()
|
|
||||||
.updateSomeTextViewIsAlreadyEditing(false);
|
|
||||||
if (widget.onUpdate != null) {
|
|
||||||
widget.onUpdate!();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTapOutside: (a) async {
|
onTapOutside: (a) async {
|
||||||
widget.layerData.text = textController.text;
|
widget.layerData.text = textController.text;
|
||||||
Future.delayed(const Duration(milliseconds: 100), () async {
|
Future.delayed(const Duration(milliseconds: 100), () async {
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,40 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool?> _showBackDialog() {
|
||||||
|
return showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(
|
||||||
|
context.lang.dialogAskDeleteMediaFilePopTitle,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
FilledButton(
|
||||||
|
child: Text(context.lang.dialogAskDeleteMediaFilePopDelete),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text(context.lang.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, false);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> askToCloseThenClose() async {
|
||||||
|
final shouldPop = await _showBackDialog() ?? false;
|
||||||
|
if (mounted && shouldPop) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> get actionsAtTheTop {
|
List<Widget> get actionsAtTheTop {
|
||||||
if (layers.isNotEmpty &&
|
if (layers.isNotEmpty &&
|
||||||
layers.last.isEditing &&
|
layers.last.isEditing &&
|
||||||
|
|
@ -275,7 +309,14 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
FontAwesomeIcons.xmark,
|
FontAwesomeIcons.xmark,
|
||||||
tooltipText: context.lang.close,
|
tooltipText: context.lang.close,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.pop(context, false);
|
final nonImageFilterLayer = layers.where(
|
||||||
|
(x) => x is! BackgroundLayerData && x is! FilterLayerData,
|
||||||
|
);
|
||||||
|
if (nonImageFilterLayer.isEmpty) {
|
||||||
|
Navigator.pop(context, false);
|
||||||
|
} else {
|
||||||
|
await askToCloseThenClose();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(child: Container()),
|
Expanded(child: Container()),
|
||||||
|
|
@ -446,153 +487,161 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
return Scaffold(
|
return PopScope<bool?>(
|
||||||
backgroundColor:
|
canPop: false,
|
||||||
widget.sharedFromGallery ? null : Colors.white.withAlpha(0),
|
onPopInvokedWithResult: (bool didPop, bool? result) async {
|
||||||
resizeToAvoidBottomInset: false,
|
if (didPop) return;
|
||||||
body: Stack(
|
await askToCloseThenClose();
|
||||||
fit: StackFit.expand,
|
},
|
||||||
children: [
|
child: Scaffold(
|
||||||
GestureDetector(
|
backgroundColor:
|
||||||
onTapDown: (details) {
|
widget.sharedFromGallery ? null : Colors.white.withAlpha(0),
|
||||||
if (details.globalPosition.dy > 60) {
|
resizeToAvoidBottomInset: false,
|
||||||
tabDownPosition = details.globalPosition.dy - 60;
|
body: Stack(
|
||||||
} else {
|
fit: StackFit.expand,
|
||||||
tabDownPosition = details.globalPosition.dy;
|
children: [
|
||||||
}
|
GestureDetector(
|
||||||
},
|
onTapDown: (details) {
|
||||||
onTap: () {
|
if (details.globalPosition.dy > 60) {
|
||||||
if (layers.any((x) => x.isEditing)) {
|
tabDownPosition = details.globalPosition.dy - 60;
|
||||||
return;
|
} else {
|
||||||
}
|
tabDownPosition = details.globalPosition.dy;
|
||||||
layers = layers.where((x) => !x.isDeleted).toList();
|
}
|
||||||
undoLayers.clear();
|
},
|
||||||
removedLayers.clear();
|
onTap: () {
|
||||||
layers.add(
|
if (layers.any((x) => x.isEditing)) {
|
||||||
TextLayerData(
|
return;
|
||||||
offset: Offset(0, tabDownPosition),
|
}
|
||||||
textLayersBefore: layers.whereType<TextLayerData>().length,
|
layers = layers.where((x) => !x.isDeleted).toList();
|
||||||
),
|
undoLayers.clear();
|
||||||
);
|
removedLayers.clear();
|
||||||
setState(() {});
|
layers.add(
|
||||||
},
|
TextLayerData(
|
||||||
child: MediaViewSizing(
|
offset: Offset(0, tabDownPosition),
|
||||||
bottomNavigation: ColoredBox(
|
textLayersBefore: layers.whereType<TextLayerData>().length,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
),
|
||||||
child: Row(
|
);
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
setState(() {});
|
||||||
children: [
|
},
|
||||||
SaveToGalleryButton(
|
child: MediaViewSizing(
|
||||||
storeImageAsOriginal: storeImageAsOriginal,
|
requiredHeight: 90,
|
||||||
mediaService: mediaService,
|
bottomNavigation: ColoredBox(
|
||||||
displayButtonLabel: widget.sendToGroup == null,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
isLoading: loadingImage,
|
child: Row(
|
||||||
),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
if (widget.sendToGroup != null) const SizedBox(width: 10),
|
children: [
|
||||||
if (widget.sendToGroup != null)
|
SaveToGalleryButton(
|
||||||
OutlinedButton(
|
storeImageAsOriginal: storeImageAsOriginal,
|
||||||
style: OutlinedButton.styleFrom(
|
mediaService: mediaService,
|
||||||
iconColor: Theme.of(context).colorScheme.primary,
|
displayButtonLabel: widget.sendToGroup == null,
|
||||||
foregroundColor:
|
isLoading: loadingImage,
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
onPressed: pushShareImageView,
|
|
||||||
child: const FaIcon(FontAwesomeIcons.userPlus),
|
|
||||||
),
|
),
|
||||||
SizedBox(width: widget.sendToGroup == null ? 20 : 10),
|
if (widget.sendToGroup != null) const SizedBox(width: 10),
|
||||||
FilledButton.icon(
|
if (widget.sendToGroup != null)
|
||||||
icon: sendingOrLoadingImage
|
OutlinedButton(
|
||||||
? SizedBox(
|
style: OutlinedButton.styleFrom(
|
||||||
height: 12,
|
iconColor: Theme.of(context).colorScheme.primary,
|
||||||
width: 12,
|
foregroundColor:
|
||||||
child: CircularProgressIndicator(
|
Theme.of(context).colorScheme.primary,
|
||||||
strokeWidth: 2,
|
),
|
||||||
color: Theme.of(context)
|
onPressed: pushShareImageView,
|
||||||
.colorScheme
|
child: const FaIcon(FontAwesomeIcons.userPlus),
|
||||||
.inversePrimary,
|
),
|
||||||
),
|
SizedBox(width: widget.sendToGroup == null ? 20 : 10),
|
||||||
)
|
FilledButton.icon(
|
||||||
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
icon: sendingOrLoadingImage
|
||||||
onPressed: () async {
|
? SizedBox(
|
||||||
if (sendingOrLoadingImage) return;
|
height: 12,
|
||||||
if (widget.sendToGroup == null) {
|
width: 12,
|
||||||
return pushShareImageView();
|
child: CircularProgressIndicator(
|
||||||
}
|
strokeWidth: 2,
|
||||||
await sendImageToSinglePerson();
|
color: Theme.of(context)
|
||||||
},
|
.colorScheme
|
||||||
style: ButtonStyle(
|
.inversePrimary,
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
),
|
||||||
const EdgeInsets.symmetric(
|
)
|
||||||
vertical: 10,
|
: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
horizontal: 30,
|
onPressed: () async {
|
||||||
|
if (sendingOrLoadingImage) return;
|
||||||
|
if (widget.sendToGroup == null) {
|
||||||
|
return pushShareImageView();
|
||||||
|
}
|
||||||
|
await sendImageToSinglePerson();
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: WidgetStateProperty.all<EdgeInsets>(
|
||||||
|
const EdgeInsets.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 30,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
label: Text(
|
||||||
|
(widget.sendToGroup == null)
|
||||||
|
? context.lang.shareImagedEditorShareWith
|
||||||
|
: substringBy(widget.sendToGroup!.groupName, 15),
|
||||||
|
style: const TextStyle(fontSize: 17),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
label: Text(
|
],
|
||||||
(widget.sendToGroup == null)
|
),
|
||||||
? context.lang.shareImagedEditorShareWith
|
|
||||||
: substringBy(widget.sendToGroup!.groupName, 15),
|
|
||||||
style: const TextStyle(fontSize: 17),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
child: SizedBox(
|
||||||
child: SizedBox(
|
height: currentImage.height / pixelRatio,
|
||||||
height: currentImage.height / pixelRatio,
|
width: currentImage.width / pixelRatio,
|
||||||
width: currentImage.width / pixelRatio,
|
child: Stack(
|
||||||
child: Stack(
|
children: [
|
||||||
children: [
|
if (videoController != null)
|
||||||
if (videoController != null)
|
Positioned.fill(
|
||||||
Positioned.fill(
|
child: VideoPlayer(videoController!),
|
||||||
child: VideoPlayer(videoController!),
|
),
|
||||||
),
|
Screenshot(
|
||||||
Screenshot(
|
controller: screenshotController,
|
||||||
controller: screenshotController,
|
child: LayersViewer(
|
||||||
child: LayersViewer(
|
layers: layers.where((x) => !x.isDeleted).toList(),
|
||||||
layers: layers.where((x) => !x.isDeleted).toList(),
|
onUpdate: () {
|
||||||
onUpdate: () {
|
for (final layer in layers) {
|
||||||
for (final layer in layers) {
|
layer.isEditing = false;
|
||||||
layer.isEditing = false;
|
if (layer.isDeleted) {
|
||||||
if (layer.isDeleted) {
|
removedLayers.add(layer);
|
||||||
removedLayers.add(layer);
|
}
|
||||||
}
|
}
|
||||||
}
|
layers = layers.where((x) => !x.isDeleted).toList();
|
||||||
layers = layers.where((x) => !x.isDeleted).toList();
|
setState(() {});
|
||||||
setState(() {});
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
Positioned(
|
top: 10,
|
||||||
top: 10,
|
left: 5,
|
||||||
left: 5,
|
right: 0,
|
||||||
right: 0,
|
|
||||||
child: SafeArea(
|
|
||||||
child: Row(
|
|
||||||
children: actionsAtTheTop,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 6,
|
|
||||||
top: 100,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Column(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
children: actionsAtTheTop,
|
||||||
children: actionsAtTheRight,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
],
|
right: 6,
|
||||||
|
top: 100,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: actionsAtTheRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,8 @@ class _UserListItem extends State<GroupListItem> {
|
||||||
: Row(
|
: Row(
|
||||||
children: [
|
children: [
|
||||||
LastMessageTime(
|
LastMessageTime(
|
||||||
dateTime: widget.group.lastMessageExchange),
|
dateTime: widget.group.lastMessageExchange,
|
||||||
|
),
|
||||||
FlameCounterWidget(
|
FlameCounterWidget(
|
||||||
groupId: widget.group.groupId,
|
groupId: widget.group.groupId,
|
||||||
prefix: true,
|
prefix: true,
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
||||||
_flameCounterSub = stream.listen((counter) {
|
_flameCounterSub = stream.listen((counter) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_flameCounter = counter -
|
// in the watchFlameCounter a one is added, so remove this here
|
||||||
1; // in the watchFlameCounter a one is added, so remove this here
|
_flameCounter = counter - 1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -84,7 +84,7 @@ class _MaxFlameListTitleState extends State<MaxFlameListTitle> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_directChat == null ||
|
if (_directChat == null ||
|
||||||
_directChat!.maxFlameCounter == 0 ||
|
_directChat!.maxFlameCounter <= 2 ||
|
||||||
_flameCounter >= _directChat!.maxFlameCounter ||
|
_flameCounter >= _directChat!.maxFlameCounter ||
|
||||||
_directChat!.maxFlameCounterFrom!
|
_directChat!.maxFlameCounterFrom!
|
||||||
.isBefore(DateTime.now().subtract(const Duration(days: 4)))) {
|
.isBefore(DateTime.now().subtract(const Duration(days: 4)))) {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,10 @@ class _MediaViewSizingState extends State<MediaViewSizing> {
|
||||||
if (widget.bottomNavigation != null) {
|
if (widget.bottomNavigation != null) {
|
||||||
if (needToDownSizeImage) {
|
if (needToDownSizeImage) {
|
||||||
imageChild = Expanded(child: imageChild);
|
imageChild = Expanded(child: imageChild);
|
||||||
bottomNavigation = widget.bottomNavigation!;
|
bottomNavigation = SizedBox(
|
||||||
|
height: widget.requiredHeight,
|
||||||
|
child: widget.bottomNavigation,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
bottomNavigation = Expanded(child: widget.bottomNavigation!);
|
bottomNavigation = Expanded(child: widget.bottomNavigation!);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
@ -14,24 +15,6 @@ class DataAndStorageView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DataAndStorageViewState extends State<DataAndStorageView> {
|
class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
Map<String, List<String>> autoDownloadOptions = defaultAutoDownloadOptions;
|
|
||||||
bool storeMediaFilesInGallery = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
|
||||||
final user = await getUser();
|
|
||||||
if (user == null) return;
|
|
||||||
setState(() {
|
|
||||||
autoDownloadOptions =
|
|
||||||
user.autoDownloadOptions ?? defaultAutoDownloadOptions;
|
|
||||||
storeMediaFilesInGallery = user.storeMediaFilesInGallery;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showAutoDownloadOptions(
|
Future<void> showAutoDownloadOptions(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ConnectivityResult connectionMode,
|
ConnectivityResult connectionMode,
|
||||||
|
|
@ -41,10 +24,11 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AutoDownloadOptionsDialog(
|
return AutoDownloadOptionsDialog(
|
||||||
autoDownloadOptions: autoDownloadOptions,
|
autoDownloadOptions:
|
||||||
|
gUser.autoDownloadOptions ?? defaultAutoDownloadOptions,
|
||||||
connectionMode: connectionMode,
|
connectionMode: connectionMode,
|
||||||
onUpdate: () async {
|
onUpdate: () async {
|
||||||
await initAsync();
|
setState(() {});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -53,14 +37,16 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
|
|
||||||
Future<void> toggleStoreInGallery() async {
|
Future<void> toggleStoreInGallery() async {
|
||||||
await updateUserdata((u) {
|
await updateUserdata((u) {
|
||||||
u.storeMediaFilesInGallery = !storeMediaFilesInGallery;
|
u.storeMediaFilesInGallery = !u.storeMediaFilesInGallery;
|
||||||
return u;
|
return u;
|
||||||
});
|
});
|
||||||
await initAsync();
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final autoDownloadOptions =
|
||||||
|
gUser.autoDownloadOptions ?? defaultAutoDownloadOptions;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.lang.settingsStorageData),
|
title: Text(context.lang.settingsStorageData),
|
||||||
|
|
@ -72,7 +58,7 @@ class _DataAndStorageViewState extends State<DataAndStorageView> {
|
||||||
subtitle: Text(context.lang.settingsStorageDataStoreInGSubtitle),
|
subtitle: Text(context.lang.settingsStorageDataStoreInGSubtitle),
|
||||||
onTap: toggleStoreInGallery,
|
onTap: toggleStoreInGallery,
|
||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: storeMediaFilesInGallery,
|
value: gUser.storeMediaFilesInGallery,
|
||||||
onChanged: (a) => toggleStoreInGallery(),
|
onChanged: (a) => toggleStoreInGallery(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -157,6 +143,14 @@ class _AutoDownloadOptionsDialogState extends State<AutoDownloadOptionsDialog> {
|
||||||
await _updateAutoDownloadSetting(DownloadMediaTypes.video, value);
|
await _updateAutoDownloadSetting(DownloadMediaTypes.video, value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: const Text('Audio'),
|
||||||
|
value: autoDownloadOptions[widget.connectionMode.name]!
|
||||||
|
.contains(DownloadMediaTypes.audio.name),
|
||||||
|
onChanged: (bool? value) async {
|
||||||
|
await _updateAutoDownloadSetting(DownloadMediaTypes.audio, value);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class _DiagnosticsViewState extends State<DiagnosticsView> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Diagnostics')),
|
appBar: AppBar(title: const Text('Diagnostics')),
|
||||||
body: FutureBuilder<String>(
|
body: FutureBuilder<String>(
|
||||||
future: loadLogFile(),
|
future: readLast1000Lines(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
|
|
@ -11,8 +12,22 @@ import 'package:twonly/src/views/settings/help/diagnostics.view.dart';
|
||||||
import 'package:twonly/src/views/settings/help/faq.view.dart';
|
import 'package:twonly/src/views/settings/help/faq.view.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class HelpView extends StatelessWidget {
|
class HelpView extends StatefulWidget {
|
||||||
const HelpView({super.key});
|
const HelpView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HelpView> createState() => _HelpViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HelpViewState extends State<HelpView> {
|
||||||
|
Future<void> toggleAllowErrorTrackingViaSentry() async {
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.allowErrorTrackingViaSentry = !u.allowErrorTrackingViaSentry;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -49,7 +64,10 @@ class HelpView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(context.lang.settingsResetTutorials),
|
title: Text(context.lang.settingsResetTutorials),
|
||||||
subtitle: Text(context.lang.settingsResetTutorialsDesc),
|
subtitle: Text(
|
||||||
|
context.lang.settingsResetTutorialsDesc,
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await updateUserdata((user) {
|
await updateUserdata((user) {
|
||||||
user.tutorialDisplayed = [];
|
user.tutorialDisplayed = [];
|
||||||
|
|
@ -65,6 +83,32 @@ class HelpView extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.allowErrorTracking),
|
||||||
|
subtitle: Text(
|
||||||
|
context.lang.allowErrorTrackingSubtitle,
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
),
|
||||||
|
onTap: toggleAllowErrorTrackingViaSentry,
|
||||||
|
trailing: Switch(
|
||||||
|
value: gUser.allowErrorTrackingViaSentry,
|
||||||
|
onChanged: (a) => toggleAllowErrorTrackingViaSentry(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(context.lang.settingsHelpDiagnostics),
|
||||||
|
onTap: () async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return const DiagnosticsView();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: PackageInfo.fromPlatform(),
|
future: PackageInfo.fromPlatform(),
|
||||||
builder: (context, snap) {
|
builder: (context, snap) {
|
||||||
|
|
@ -97,19 +141,6 @@ class HelpView extends StatelessWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
title: Text(context.lang.settingsHelpDiagnostics),
|
|
||||||
onTap: () async {
|
|
||||||
await Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) {
|
|
||||||
return const DiagnosticsView();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: const Text('Changelog'),
|
title: const Text('Changelog'),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,8 @@ class _DatabaseMigrationViewState extends State<DatabaseMigrationView> {
|
||||||
Uint8List? avatarSvg;
|
Uint8List? avatarSvg;
|
||||||
if (oldContact.avatarSvg != null) {
|
if (oldContact.avatarSvg != null) {
|
||||||
avatarSvg = Uint8List.fromList(
|
avatarSvg = Uint8List.fromList(
|
||||||
gzip.encode(utf8.encode(oldContact.avatarSvg!)));
|
gzip.encode(utf8.encode(oldContact.avatarSvg!)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await twonlyDB.contactsDao.insertContact(
|
await twonlyDB.contactsDao.insertContact(
|
||||||
ContactsCompanion(
|
ContactsCompanion(
|
||||||
|
|
|
||||||
32
pubspec.lock
32
pubspec.lock
|
|
@ -976,6 +976,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
jni:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: jni
|
||||||
|
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.2"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1160,6 +1168,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
|
objective_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: objective_c
|
||||||
|
sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.0"
|
||||||
octo_image:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1440,6 +1456,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.8"
|
version: "0.3.8"
|
||||||
|
sentry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sentry
|
||||||
|
sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.8.0"
|
||||||
|
sentry_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sentry_flutter
|
||||||
|
sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.8.0"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
26
pubspec.yaml
26
pubspec.yaml
|
|
@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 0.0.66+66
|
version: 0.0.67+67
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.6.0
|
sdk: ^3.6.0
|
||||||
|
|
@ -69,6 +69,7 @@ dependencies:
|
||||||
restart_app: ^1.3.2
|
restart_app: ^1.3.2
|
||||||
screenshot: ^3.0.0
|
screenshot: ^3.0.0
|
||||||
scrollable_positioned_list: ^0.3.8
|
scrollable_positioned_list: ^0.3.8
|
||||||
|
sentry_flutter: ^9.8.0
|
||||||
share_plus: ^12.0.0
|
share_plus: ^12.0.0
|
||||||
tutorial_coach_mark: ^1.3.0
|
tutorial_coach_mark: ^1.3.0
|
||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
|
|
@ -76,25 +77,22 @@ dependencies:
|
||||||
web_socket_channel: ^3.0.1
|
web_socket_channel: ^3.0.1
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
|
|
||||||
flutter_secure_storage_darwin:
|
|
||||||
git:
|
|
||||||
url: https://github.com/juliansteenbakker/flutter_secure_storage.git
|
|
||||||
ref: a06ead81809c900e7fc421a30db0adf3b5919139 # from develop
|
|
||||||
path: flutter_secure_storage_darwin/
|
|
||||||
|
|
||||||
flutter_android_volume_keydown:
|
|
||||||
git:
|
|
||||||
url: https://github.com/yenchieh/flutter_android_volume_keydown.git
|
|
||||||
branch: fix/lStar-not-found-error
|
|
||||||
|
|
||||||
# hardcoding the mirror mode of the VideCapture to MIRROR_MODE_ON_FRONT_ONLY
|
|
||||||
camera_android_camerax:
|
camera_android_camerax:
|
||||||
# path: ../flutter-packages/packages/camera/camera_android_camerax
|
# path: ../flutter-packages/packages/camera/camera_android_camerax
|
||||||
git:
|
git:
|
||||||
url: https://github.com/otsmr/flutter-packages.git
|
url: https://github.com/otsmr/flutter-packages.git
|
||||||
path: packages/camera/camera_android_camerax
|
path: packages/camera/camera_android_camerax
|
||||||
ref: aef58af205a5f3ce6588a5c59bb2e734aab943f0
|
ref: aef58af205a5f3ce6588a5c59bb2e734aab943f0
|
||||||
|
flutter_android_volume_keydown:
|
||||||
|
git:
|
||||||
|
url: https://github.com/yenchieh/flutter_android_volume_keydown.git
|
||||||
|
branch: fix/lStar-not-found-error
|
||||||
|
flutter_secure_storage_darwin:
|
||||||
|
git:
|
||||||
|
url: https://github.com/juliansteenbakker/flutter_secure_storage.git
|
||||||
|
ref: a06ead81809c900e7fc421a30db0adf3b5919139 # from develop
|
||||||
|
path: flutter_secure_storage_darwin/
|
||||||
|
# hardcoding the mirror mode of the VideCapture to MIRROR_MODE_ON_FRONT_ONLY
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.15
|
build_runner: ^2.4.15
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue