add troubleshooting for notifications & video stab

This commit is contained in:
otsmr 2026-04-05 16:22:48 +02:00
parent c48ce31bc6
commit 955992d5d2
25 changed files with 763 additions and 479 deletions

View file

@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.1.2
- New: Developer settings to reduce flames
- Improve: Improved troubleshooting for issues with push notifications
- Fix: Flash not activated when starting a video recording
## 0.1.1 ## 0.1.1
- New: Groups can now collect flames as well - New: Groups can now collect flames as well

View file

@ -6,7 +6,8 @@ class Routes {
static const String chatsStartNewChat = '/chats/start_new_chat'; static const String chatsStartNewChat = '/chats/start_new_chat';
static const String chatsCameraSendTo = '/chats/camera_send_to'; static const String chatsCameraSendTo = '/chats/camera_send_to';
static const String chatsMediaViewer = '/chats/media_viewer'; static const String chatsMediaViewer = '/chats/media_viewer';
static const String chatsMessages = '/chats/messages';
static String chatsMessages(String groupId) => '/chats/messages/$groupId';
static String groupCreateSelectMember(String? groupId) => static String groupCreateSelectMember(String? groupId) =>
'/group/create/select_member${groupId == null ? '' : '/$groupId'}'; '/group/create/select_member${groupId == null ? '' : '/$groupId'}';
@ -53,5 +54,7 @@ class Routes {
'/settings/developer/retransmission_database'; '/settings/developer/retransmission_database';
static const String settingsDeveloperAutomatedTesting = static const String settingsDeveloperAutomatedTesting =
'/settings/developer/automated_testing'; '/settings/developer/automated_testing';
static const String settingsDeveloperReduceFlames =
'/settings/developer/reduce_flames';
static const String settingsInvite = '/settings/invite'; static const String settingsInvite = '/settings/invite';
} }

View file

@ -733,9 +733,33 @@ abstract class AppLocalizations {
/// No description provided for @settingsNotifyTroubleshootingNoProblemDesc. /// No description provided for @settingsNotifyTroubleshootingNoProblemDesc.
/// ///
/// In en, this message translates to: /// 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.'** /// **'Press OK to receive a test notification. If you do not receive the test notification, please click on the new menu item that appears after you click “OK”.'**
String get settingsNotifyTroubleshootingNoProblemDesc; String get settingsNotifyTroubleshootingNoProblemDesc;
/// No description provided for @settingsNotifyResetTitle.
///
/// In en, this message translates to:
/// **'Didn\'t receive a test notification?'**
String get settingsNotifyResetTitle;
/// No description provided for @settingsNotifyResetTitleSubtitle.
///
/// In en, this message translates to:
/// **'If you haven\'t received any test notifications, click here to reset your notification tokens.'**
String get settingsNotifyResetTitleSubtitle;
/// No description provided for @settingsNotifyResetTitleReset.
///
/// In en, this message translates to:
/// **'Your notification tokens have been reset.'**
String get settingsNotifyResetTitleReset;
/// No description provided for @settingsNotifyResetTitleResetDesc.
///
/// In en, this message translates to:
/// **'If the problem persists, please send us your debug log via Settings > Help so we can investigate the issue.'**
String get settingsNotifyResetTitleResetDesc;
/// No description provided for @settingsHelp. /// No description provided for @settingsHelp.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -356,7 +356,22 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get settingsNotifyTroubleshootingNoProblemDesc => 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.'; 'Um eine Testbenachrichtigung zu erhalten, klicke auf OK. Falls du die Testbenachrichtigung nicht erhältst, klicke bitte auf den neuen Menüpunkt, der nach dem Klicken auf „OK“ angezeigt wird.';
@override
String get settingsNotifyResetTitle => 'Keine Testbenachrichtigung erhalten?';
@override
String get settingsNotifyResetTitleSubtitle =>
'Falls du keine Testbenachrichtigungen erhalten hast, klicke hier, um deine Benachrichtigungstoken zurückzusetzen.';
@override
String get settingsNotifyResetTitleReset =>
'Deine Benachrichtigungstoken wurden zurückgesetzt.';
@override
String get settingsNotifyResetTitleResetDesc =>
'Sollte das Problem weiterhin bestehen, sende uns bitte dein Debug-Protokoll über „Einstellungen“ > „Hilfe“, damit wir das Problem untersuchen können.';
@override @override
String get settingsHelp => 'Hilfe'; String get settingsHelp => 'Hilfe';

View file

@ -351,7 +351,22 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get settingsNotifyTroubleshootingNoProblemDesc => 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.'; 'Press OK to receive a test notification. If you do not receive the test notification, please click on the new menu item that appears after you click “OK”.';
@override
String get settingsNotifyResetTitle => 'Didn\'t receive a test notification?';
@override
String get settingsNotifyResetTitleSubtitle =>
'If you haven\'t received any test notifications, click here to reset your notification tokens.';
@override
String get settingsNotifyResetTitleReset =>
'Your notification tokens have been reset.';
@override
String get settingsNotifyResetTitleResetDesc =>
'If the problem persists, please send us your debug log via Settings > Help so we can investigate the issue.';
@override @override
String get settingsHelp => 'Help'; String get settingsHelp => 'Help';

View file

@ -351,7 +351,22 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get settingsNotifyTroubleshootingNoProblemDesc => 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.'; 'Press OK to receive a test notification. If you do not receive the test notification, please click on the new menu item that appears after you click “OK”.';
@override
String get settingsNotifyResetTitle => 'Didn\'t receive a test notification?';
@override
String get settingsNotifyResetTitleSubtitle =>
'If you haven\'t received any test notifications, click here to reset your notification tokens.';
@override
String get settingsNotifyResetTitleReset =>
'Your notification tokens have been reset.';
@override
String get settingsNotifyResetTitleResetDesc =>
'If the problem persists, please send us your debug log via Settings > Help so we can investigate the issue.';
@override @override
String get settingsHelp => 'Help'; String get settingsHelp => 'Help';

View file

@ -53,6 +53,9 @@ class UserData {
@JsonKey(defaultValue: false) @JsonKey(defaultValue: false)
bool requestedAudioPermission = false; bool requestedAudioPermission = false;
@JsonKey(defaultValue: false)
bool videoStabilizationEnabled = false;
@JsonKey(defaultValue: true) @JsonKey(defaultValue: true)
bool showFeedbackShortcut = true; bool showFeedbackShortcut = true;

View file

@ -30,6 +30,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt() ..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
..requestedAudioPermission = ..requestedAudioPermission =
json['requestedAudioPermission'] as bool? ?? false json['requestedAudioPermission'] as bool? ?? false
..videoStabilizationEnabled =
json['videoStabilizationEnabled'] as bool? ?? false
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true ..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
..showShowImagePreviewWhenSending = ..showShowImagePreviewWhenSending =
json['showShowImagePreviewWhenSending'] as bool? ?? false json['showShowImagePreviewWhenSending'] as bool? ?? false
@ -105,6 +107,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'defaultShowTime': instance.defaultShowTime, 'defaultShowTime': instance.defaultShowTime,
'requestedAudioPermission': instance.requestedAudioPermission, 'requestedAudioPermission': instance.requestedAudioPermission,
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
'showFeedbackShortcut': instance.showFeedbackShortcut, 'showFeedbackShortcut': instance.showFeedbackShortcut,
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending, 'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
'startWithCameraOpen': instance.startWithCameraOpen, 'startWithCameraOpen': instance.startWithCameraOpen,

View file

@ -26,6 +26,7 @@ import 'package:twonly/src/views/settings/data_and_storage/export_media.view.dar
import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart'; import 'package:twonly/src/views/settings/data_and_storage/import_media.view.dart';
import 'package:twonly/src/views/settings/developer/automated_testing.view.dart'; import 'package:twonly/src/views/settings/developer/automated_testing.view.dart';
import 'package:twonly/src/views/settings/developer/developer.view.dart'; import 'package:twonly/src/views/settings/developer/developer.view.dart';
import 'package:twonly/src/views/settings/developer/reduce_flames.view.dart';
import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart'; import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart';
import 'package:twonly/src/views/settings/help/changelog.view.dart'; import 'package:twonly/src/views/settings/help/changelog.view.dart';
import 'package:twonly/src/views/settings/help/contact_us.view.dart'; import 'package:twonly/src/views/settings/help/contact_us.view.dart';
@ -84,10 +85,10 @@ final routerProvider = GoRouter(
}, },
), ),
GoRoute( GoRoute(
path: 'messages', path: 'messages/:groupId',
builder: (context, state) { builder: (context, state) {
final group = state.extra! as Group; final groupId = state.pathParameters['groupId']!;
return ChatMessagesView(group); return ChatMessagesView(groupId);
}, },
), ),
], ],
@ -280,6 +281,10 @@ final routerProvider = GoRouter(
path: 'automated_testing', path: 'automated_testing',
builder: (context, state) => const AutomatedTestingView(), builder: (context, state) => const AutomatedTestingView(),
), ),
GoRoute(
path: 'reduce_flames',
builder: (context, state) => const ReduceFlamesView(),
),
], ],
), ),
GoRoute( GoRoute(

View file

@ -280,6 +280,14 @@ Future<void> _createUploadRequest(MediaFileService media) async {
} }
} }
final contact = await twonlyDB.contactsDao.getContactById(
groupMember.contactId,
);
if (contact == null || contact.accountDeleted) {
continue;
}
final downloadToken = getRandomUint8List(32); final downloadToken = getRandomUint8List(32);
late EncryptedContent_Media_Type type; late EncryptedContent_Media_Type type;
@ -329,10 +337,11 @@ Future<void> _createUploadRequest(MediaFileService media) async {
Log.error( Log.error(
'Could not generate ciphertext message for ${groupMember.contactId}', 'Could not generate ciphertext message for ${groupMember.contactId}',
); );
continue;
} }
final messageOnSuccess = TextMessage() final messageOnSuccess = TextMessage()
..body = cipherText!.$1 ..body = cipherText.$1
..userId = Int64(groupMember.contactId); ..userId = Int64(groupMember.contactId);
if (cipherText.$2 != null) { if (cipherText.$2 != null) {

View file

@ -13,8 +13,9 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
final groups = await twonlyDB.groupsDao.getAllGroups(); final groups = await twonlyDB.groupsDao.getAllGroups();
if (groups.isEmpty) return; if (groups.isEmpty) return;
final maxMessageCounter = groups.map((x) => x.totalMediaCounter).max; final maxMessageCounter = groups.map((x) => x.totalMediaCounter).max;
final bestFriend = final bestFriend = groups.firstWhere(
groups.firstWhere((x) => x.totalMediaCounter == maxMessageCounter); (x) => x.totalMediaCounter == maxMessageCounter,
);
if (gUser.myBestFriendGroupId != bestFriend.groupId) { if (gUser.myBestFriendGroupId != bestFriend.groupId) {
await updateUserdata((user) { await updateUserdata((user) {
@ -42,8 +43,9 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
EncryptedContent( EncryptedContent(
flameSync: EncryptedContent_FlameSync( flameSync: EncryptedContent_FlameSync(
flameCounter: Int64(flameCounter), flameCounter: Int64(flameCounter),
lastFlameCounterChange: lastFlameCounterChange: Int64(
Int64(group.lastFlameCounterChange!.millisecondsSinceEpoch), group.lastFlameCounterChange!.millisecondsSinceEpoch,
),
bestFriend: group.groupId == bestFriend.groupId, bestFriend: group.groupId == bestFriend.groupId,
forceUpdate: group.groupId == forceForGroup, forceUpdate: group.groupId == forceForGroup,
), ),
@ -134,8 +136,9 @@ Future<void> incFlameCounter(
// Overwrite max flame counter either the current is bigger or the the max flame counter is older then 4 days // Overwrite max flame counter either the current is bigger or the the max flame counter is older then 4 days
if (flameCounter >= maxFlameCounter || if (flameCounter >= maxFlameCounter ||
maxFlameCounterFrom == null || maxFlameCounterFrom == null ||
maxFlameCounterFrom maxFlameCounterFrom.isBefore(
.isBefore(clock.now().subtract(const Duration(days: 5)))) { clock.now().subtract(const Duration(days: 5)),
)) {
maxFlameCounter = flameCounter; maxFlameCounter = flameCounter;
maxFlameCounterFrom = clock.now(); maxFlameCounterFrom = clock.now();
} }
@ -172,6 +175,7 @@ bool isItPossibleToRestoreFlames(Group group) {
final flameCounter = getFlameCounterFromGroup(group); final flameCounter = getFlameCounterFromGroup(group);
return group.maxFlameCounter > 2 && return group.maxFlameCounter > 2 &&
flameCounter < group.maxFlameCounter && flameCounter < group.maxFlameCounter &&
group.maxFlameCounterFrom! group.maxFlameCounterFrom!.isAfter(
.isAfter(clock.now().subtract(const Duration(days: 5))); clock.now().subtract(const Duration(days: 5)),
);
} }

View file

@ -3,6 +3,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io' show Platform; import 'dart:io' show Platform;
import 'package:firebase_app_installations/firebase_app_installations.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -41,22 +42,31 @@ Future<void> checkForTokenUpdates() async {
Log.error('Could not get fcm token'); Log.error('Could not get fcm token');
return; return;
} }
Log.info('Loaded fcm token');
Log.info('Loaded FCM token.');
if (storedToken == null || fcmToken != storedToken) { if (storedToken == null || fcmToken != storedToken) {
Log.info('Got new FCM TOKEN.');
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
await updateUserdata((u) { await updateUserdata((u) {
u.updateFCMToken = true; u.updateFCMToken = true;
return u; return u;
}); });
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
} }
FirebaseMessaging.instance.onTokenRefresh.listen((fcmToken) async { FirebaseMessaging.instance.onTokenRefresh
.listen((fcmToken) async {
Log.info('Got new FCM TOKEN.');
await storage.write(
key: SecureStorageKeys.googleFcm,
value: fcmToken,
);
await updateUserdata((u) { await updateUserdata((u) {
u.updateFCMToken = true; u.updateFCMToken = true;
return u; return u;
}); });
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken); })
}).onError((err) { .onError((err) {
Log.error('could not listen on token refresh'); Log.error('could not listen on token refresh');
}); });
} catch (e) { } catch (e) {
@ -64,21 +74,35 @@ Future<void> checkForTokenUpdates() async {
} }
} }
Future<void> initFCMAfterAuthenticated() async { Future<void> initFCMAfterAuthenticated({bool force = false}) async {
if (gUser.updateFCMToken) { if (gUser.updateFCMToken || force) {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm); final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
if (storedToken != null) { if (storedToken != null) {
final res = await apiService.updateFCMToken(storedToken); final res = await apiService.updateFCMToken(storedToken);
if (res.isSuccess) { if (res.isSuccess) {
Log.info('Uploaded new fmt token!'); Log.info('Uploaded new FCM token!');
await updateUserdata((u) { await updateUserdata((u) {
u.updateFCMToken = false; u.updateFCMToken = false;
return u; return u;
}); });
} else {
Log.error('Could not update FCM token!');
}
} else {
Log.error('Could not send FCM update to server as token is empty.');
} }
} }
} }
Future<void> resetFCMTokens() async {
await FirebaseInstallations.instance.delete();
Log.info('Firebase Installation successfully deleted.');
await FirebaseMessaging.instance.deleteToken();
Log.info('Old FCM deleted.');
await const FlutterSecureStorage().delete(key: SecureStorageKeys.googleFcm);
await checkForTokenUpdates();
await initFCMAfterAuthenticated(force: true);
} }
Future<void> initFCMService() async { Future<void> initFCMService() async {

View file

@ -129,9 +129,9 @@ Future<void> cleanLogFile() async {
if (logFile.existsSync()) { if (logFile.existsSync()) {
final lines = await logFile.readAsLines(); final lines = await logFile.readAsLines();
if (lines.length <= 10000) return; if (lines.length <= 100000) return;
final removeCount = lines.length - 10000; final removeCount = lines.length - 100000;
final remaining = lines.sublist(removeCount, lines.length); final remaining = lines.sublist(removeCount, lines.length);
final sink = logFile.openWrite()..writeAll(remaining, '\n'); final sink = logFile.openWrite()..writeAll(remaining, '\n');

View file

@ -181,8 +181,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
// Maybe this is the reason? // Maybe this is the reason?
return; return;
} else { } else {
androidVolumeDownSub = androidVolumeDownSub = FlutterAndroidVolumeKeydown.stream.listen((
FlutterAndroidVolumeKeydown.stream.listen((event) { event,
) {
if (widget.isVisible) { if (widget.isVisible) {
takePicture(); takePicture();
} else { } else {
@ -297,8 +298,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
return; return;
} }
final image = await mc.screenshotController final image = await mc.screenshotController.capture(
.capture(pixelRatio: MediaQuery.of(context).devicePixelRatio); pixelRatio: MediaQuery.of(context).devicePixelRatio,
);
if (await pushMediaEditor(image, null)) { if (await pushMediaEditor(image, null)) {
return; return;
@ -314,7 +316,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
bool sharedFromGallery = false, bool sharedFromGallery = false,
MediaType? mediaType, MediaType? mediaType,
}) async { }) async {
final type = mediaType ?? final type =
mediaType ??
((videoFilePath != null) ? MediaType.video : MediaType.image); ((videoFilePath != null) ? MediaType.video : MediaType.image);
final mediaFileService = await initializeMediaUpload( final mediaFileService = await initializeMediaUpload(
type, type,
@ -340,7 +343,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
await deInitVolumeControl(); await deInitVolumeControl();
if (!mounted) return true; if (!mounted) return true;
final shouldReturn = await Navigator.push( final shouldReturn =
await Navigator.push(
context, context,
PageRouteBuilder( PageRouteBuilder(
opaque: false, opaque: false,
@ -352,13 +356,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
mainCameraController: mc, mainCameraController: mc,
previewLink: mc.sharedLinkForPreview, previewLink: mc.sharedLinkForPreview,
), ),
transitionsBuilder: (context, animation, secondaryAnimation, child) { transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return child; return child;
}, },
transitionDuration: Duration.zero, transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero, reverseTransitionDuration: Duration.zero,
), ),
) as bool?; )
as bool?;
if (mounted) { if (mounted) {
setState(() { setState(() {
mc.isSharePreviewIsShown = false; mc.isSharePreviewIsShown = false;
@ -396,13 +402,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
return; return;
} }
mc.selectedCameraDetails.scaleFactor = (_baseScaleFactor + mc.selectedCameraDetails.scaleFactor =
(_baseScaleFactor +
// ignore: avoid_dynamic_calls // ignore: avoid_dynamic_calls
(_basePanY - (details.localPosition.dy as double)) / 30) (_basePanY - (details.localPosition.dy as double)) / 30)
.clamp(1, mc.selectedCameraDetails.maxAvailableZoom); .clamp(1, mc.selectedCameraDetails.maxAvailableZoom);
await mc.cameraController! await mc.cameraController!.setZoomLevel(
.setZoomLevel(mc.selectedCameraDetails.scaleFactor); mc.selectedCameraDetails.scaleFactor,
);
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
@ -434,8 +442,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
ScreenshotImage? image; ScreenshotImage? image;
MediaType? mediaType; MediaType? mediaType;
final isImage = final isImage = imageExtensions.any(
imageExtensions.any((ext) => pickedFile.name.contains(ext)); (ext) => pickedFile.name.contains(ext),
);
if (isImage) { if (isImage) {
if (pickedFile.name.contains('.gif')) { if (pickedFile.name.contains('.gif')) {
mediaType = MediaType.gif; mediaType = MediaType.gif;
@ -497,10 +506,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
mc.isVideoRecording = true; mc.isVideoRecording = true;
}); });
if (mc.selectedCameraDetails.isFlashOn) {
await mc.cameraController?.setFlashMode(FlashMode.torch);
}
try { try {
await mc.cameraController?.startVideoRecording(); await mc.cameraController?.startVideoRecording();
_videoRecordingTimer = _videoRecordingTimer = Timer.periodic(const Duration(milliseconds: 15), (
Timer.periodic(const Duration(milliseconds: 15), (timer) { timer,
) {
setState(() { setState(() {
_currentTime = clock.now(); _currentTime = clock.now();
}); });
@ -521,6 +535,7 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
mc.isVideoRecording = false; mc.isVideoRecording = false;
}); });
_showCameraException(e); _showCameraException(e);
await mc.cameraController?.setFlashMode(FlashMode.off);
return; return;
} }
} }
@ -531,6 +546,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
_videoRecordingTimer = null; _videoRecordingTimer = null;
} }
await mc.cameraController?.setFlashMode(FlashMode.off);
setState(() { setState(() {
_videoRecordingStarted = null; _videoRecordingStarted = null;
mc.isVideoRecording = false; mc.isVideoRecording = false;
@ -601,8 +618,12 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
keyTriggerButton.currentContext!.findRenderObject()! as RenderBox; keyTriggerButton.currentContext!.findRenderObject()! as RenderBox;
final localPosition = renderBox.globalToLocal(details.globalPosition); final localPosition = renderBox.globalToLocal(details.globalPosition);
final containerRect = final containerRect = Rect.fromLTWH(
Rect.fromLTWH(0, 0, renderBox.size.width, renderBox.size.height); 0,
0,
renderBox.size.width,
renderBox.size.height,
);
if (containerRect.contains(localPosition)) { if (containerRect.contains(localPosition)) {
startVideoRecording(); startVideoRecording();
@ -676,12 +697,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
: Colors.white.withAlpha(160), : Colors.white.withAlpha(160),
onPressed: () async { onPressed: () async {
if (mc.selectedCameraDetails.isFlashOn) { if (mc.selectedCameraDetails.isFlashOn) {
await mc.cameraController await mc.cameraController?.setFlashMode(
?.setFlashMode(FlashMode.off); FlashMode.off,
);
mc.selectedCameraDetails.isFlashOn = false; mc.selectedCameraDetails.isFlashOn = false;
} else { } else {
await mc.cameraController await mc.cameraController?.setFlashMode(
?.setFlashMode(FlashMode.always); FlashMode.always,
);
mc.selectedCameraDetails.isFlashOn = true; mc.selectedCameraDetails.isFlashOn = true;
} }
setState(() {}); setState(() {});
@ -786,7 +809,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
mc.isSelectingFaceFilters mc.isSelectingFaceFilters
? mc.currentFilterType.index == ? mc.currentFilterType.index ==
FaceFilterType FaceFilterType
.values.length - .values
.length -
1 1
? FontAwesomeIcons.xmark ? FontAwesomeIcons.xmark
: FontAwesomeIcons.arrowRight : FontAwesomeIcons.arrowRight
@ -936,10 +960,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
: 'assets/animations/failed.lottie', : 'assets/animations/failed.lottie',
repeat: false, repeat: false,
onLoaded: (p0) { onLoaded: (p0) {
Future.delayed(const Duration(seconds: 4), Future.delayed(
const Duration(seconds: 4),
() { () {
widget.mainCameraController.setState(); widget.mainCameraController
}); .setState();
},
);
}, },
), ),
), ),

View file

@ -133,6 +133,11 @@ class MainCameraController {
await cameraController?.initialize(); await cameraController?.initialize();
await cameraController?.startImageStream(_processCameraImage); await cameraController?.startImageStream(_processCameraImage);
await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor); await cameraController?.setZoomLevel(selectedCameraDetails.scaleFactor);
if (gUser.videoStabilizationEnabled) {
await cameraController?.setVideoStabilizationMode(
VideoStabilizationMode.level1,
);
}
} else { } else {
try { try {
if (!isVideoRecording) { if (!isVideoRecording) {

View file

@ -88,11 +88,13 @@ class _UserListItem extends State<GroupListItem> {
}); });
}); });
_lastMediaFilesStream = _lastMediaFilesStream = twonlyDB.mediaFilesDao
twonlyDB.mediaFilesDao.watchNewestMediaFiles().listen((mediaFiles) { .watchNewestMediaFiles()
.listen((mediaFiles) {
for (final mediaFile in mediaFiles) { for (final mediaFile in mediaFiles) {
final index = _previewMediaFiles final index = _previewMediaFiles.indexWhere(
.indexWhere((t) => t.mediaId == mediaFile.mediaId); (t) => t.mediaId == mediaFile.mediaId,
);
if (index >= 0) { if (index >= 0) {
_previewMediaFiles[index] = mediaFile; _previewMediaFiles[index] = mediaFile;
} }
@ -113,8 +115,9 @@ class _UserListItem extends State<GroupListItem> {
_previewMessages = []; _previewMessages = [];
} else if (newMessagesNotOpened.isNotEmpty) { } else if (newMessagesNotOpened.isNotEmpty) {
// Filter for the preview non opened messages. First messages which where send but not yet opened by the other side. // Filter for the preview non opened messages. First messages which where send but not yet opened by the other side.
final receivedMessages = final receivedMessages = newMessagesNotOpened
newMessagesNotOpened.where((x) => x.senderId != null).toList(); .where((x) => x.senderId != null)
.toList();
if (receivedMessages.isNotEmpty) { if (receivedMessages.isNotEmpty) {
_previewMessages = receivedMessages; _previewMessages = receivedMessages;
@ -145,8 +148,9 @@ class _UserListItem extends State<GroupListItem> {
for (final message in _previewMessages) { for (final message in _previewMessages) {
if (message.mediaId != null && if (message.mediaId != null &&
!_previewMediaFiles.any((t) => t.mediaId == message.mediaId)) { !_previewMediaFiles.any((t) => t.mediaId == message.mediaId)) {
final mediaFile = final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
await twonlyDB.mediaFilesDao.getMediaFileById(message.mediaId!); message.mediaId!,
);
if (mediaFile != null) { if (mediaFile != null) {
_previewMediaFiles.add(mediaFile); _previewMediaFiles.add(mediaFile);
} }
@ -171,8 +175,9 @@ class _UserListItem extends State<GroupListItem> {
final msgs = _previewMessages final msgs = _previewMessages
.where((x) => x.type == MessageType.media.name) .where((x) => x.type == MessageType.media.name)
.toList(); .toList();
final mediaFile = final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
await twonlyDB.mediaFilesDao.getMediaFileById(msgs.first.mediaId!); msgs.first.mediaId!,
);
if (mediaFile?.type != MediaType.audio) { if (mediaFile?.type != MediaType.audio) {
if (mediaFile?.downloadState == null) return; if (mediaFile?.downloadState == null) return;
if (mediaFile!.downloadState! == DownloadState.pending) { if (mediaFile!.downloadState! == DownloadState.pending) {
@ -190,10 +195,7 @@ class _UserListItem extends State<GroupListItem> {
} }
} }
if (!mounted) return; if (!mounted) return;
await context.push( await context.push(Routes.chatsMessages(widget.group.groupId));
Routes.chatsMessages,
extra: widget.group,
);
} }
@override @override
@ -239,8 +241,9 @@ class _UserListItem extends State<GroupListItem> {
leading: GestureDetector( leading: GestureDetector(
onTap: () async { onTap: () async {
if (widget.group.isDirectChat) { if (widget.group.isDirectChat) {
final contacts = await twonlyDB.groupsDao final contacts = await twonlyDB.groupsDao.getGroupContact(
.getGroupContact(widget.group.groupId); widget.group.groupId,
);
if (!context.mounted) return; if (!context.mounted) return;
await context.push(Routes.profileContact(contacts.first.userId)); await context.push(Routes.profileContact(contacts.first.userId));
return; return;
@ -253,12 +256,16 @@ class _UserListItem extends State<GroupListItem> {
trailing: (widget.group.leftGroup) trailing: (widget.group.leftGroup)
? null ? null
: IconButton( : IconButton(
onPressed: () => context.push( onPressed: () {
_hasNonOpenedMediaFile if (_hasNonOpenedMediaFile) {
? Routes.chatsMessages context.push(Routes.chatsMessages(widget.group.groupId));
: Routes.chatsCameraSendTo, } else {
context.push(
Routes.chatsCameraSendTo,
extra: widget.group, extra: widget.group,
), );
}
},
icon: FaIcon( icon: FaIcon(
_hasNonOpenedMediaFile _hasNonOpenedMediaFile
? FontAwesomeIcons.solidComments ? FontAwesomeIcons.solidComments

View file

@ -23,40 +23,11 @@ import 'package:twonly/src/views/components/blink.component.dart';
import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/flame.dart';
import 'package:twonly/src/views/components/verified_shield.dart'; import 'package:twonly/src/views/components/verified_shield.dart';
Color getMessageColor(Message message) {
return (message.senderId == null)
? const Color.fromARGB(255, 58, 136, 102)
: const Color.fromARGB(233, 68, 137, 255);
}
class ChatItem {
const ChatItem._({
this.message,
this.date,
this.groupAction,
});
factory ChatItem.date(DateTime date) {
return ChatItem._(date: date);
}
factory ChatItem.message(Message message) {
return ChatItem._(message: message);
}
factory ChatItem.groupAction(GroupHistory groupAction) {
return ChatItem._(groupAction: groupAction);
}
final GroupHistory? groupAction;
final Message? message;
final DateTime? date;
bool get isMessage => message != null;
bool get isDate => date != null;
bool get isGroupAction => groupAction != null;
}
/// Displays detailed information about a SampleItem. /// Displays detailed information about a SampleItem.
class ChatMessagesView extends StatefulWidget { class ChatMessagesView extends StatefulWidget {
const ChatMessagesView(this.group, {super.key}); const ChatMessagesView(this.groupId, {super.key});
final Group group; final String groupId;
@override @override
State<ChatMessagesView> createState() => _ChatMessagesViewState(); State<ChatMessagesView> createState() => _ChatMessagesViewState();
@ -64,12 +35,13 @@ class ChatMessagesView extends StatefulWidget {
class _ChatMessagesViewState extends State<ChatMessagesView> { class _ChatMessagesViewState extends State<ChatMessagesView> {
HashSet<int> alreadyReportedOpened = HashSet<int>(); HashSet<int> alreadyReportedOpened = HashSet<int>();
late Group group;
late StreamSubscription<Group?> userSub; late StreamSubscription<Group?> userSub;
late StreamSubscription<List<Message>> messageSub; late StreamSubscription<List<Message>> messageSub;
StreamSubscription<List<GroupHistory>>? groupActionsSub; StreamSubscription<List<GroupHistory>>? groupActionsSub;
StreamSubscription<List<Contact>>? contactSub; StreamSubscription<List<Contact>>? contactSub;
Group? _group;
Map<int, Contact> userIdToContact = {}; Map<int, Contact> userIdToContact = {};
List<ChatItem> messages = []; List<ChatItem> messages = [];
@ -85,7 +57,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
group = widget.group;
textFieldFocus = FocusNode(); textFieldFocus = FocusNode();
initStreams(); initStreams();
} }
@ -102,16 +73,18 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
Mutex protectMessageUpdating = Mutex(); Mutex protectMessageUpdating = Mutex();
Future<void> initStreams() async { Future<void> initStreams() async {
final groupStream = twonlyDB.groupsDao.watchGroup(group.groupId); final groupStream = twonlyDB.groupsDao.watchGroup(widget.groupId);
userSub = groupStream.listen((newGroup) { userSub = groupStream.listen((newGroup) {
if (newGroup == null) return; if (newGroup == null) return;
setState(() { setState(() {
group = newGroup; _group = newGroup;
});
}); });
if (!widget.group.isDirectChat) { protectMessageUpdating.protect(() async {
final actionsStream = twonlyDB.groupsDao.watchGroupActions(group.groupId); if (groupActionsSub == null && !newGroup.isDirectChat) {
final actionsStream = twonlyDB.groupsDao.watchGroupActions(
newGroup.groupId,
);
groupActionsSub = actionsStream.listen((update) async { groupActionsSub = actionsStream.listen((update) async {
groupActions = update; groupActions = update;
await setMessages(allMessages, update); await setMessages(allMessages, update);
@ -124,8 +97,10 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
} }
}); });
} }
});
});
final msgStream = twonlyDB.messagesDao.watchByGroupId(group.groupId); final msgStream = twonlyDB.messagesDao.watchByGroupId(widget.groupId);
messageSub = msgStream.listen((update) async { messageSub = msgStream.listen((update) async {
allMessages = update; allMessages = update;
await protectMessageUpdating.protect(() async { await protectMessageUpdating.protect(() async {
@ -153,8 +128,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
if (groupHistoryIndex < groupActions.length) { if (groupHistoryIndex < groupActions.length) {
for (; groupHistoryIndex < groupActions.length; groupHistoryIndex++) { for (; groupHistoryIndex < groupActions.length; groupHistoryIndex++) {
if (msg.createdAt.isAfter(groupActions[groupHistoryIndex].actionAt)) { if (msg.createdAt.isAfter(groupActions[groupHistoryIndex].actionAt)) {
chatItems chatItems.add(
.add(ChatItem.groupAction(groupActions[groupHistoryIndex])); ChatItem.groupAction(groupActions[groupHistoryIndex]),
);
// groupHistoryIndex++; // groupHistoryIndex++;
} else { } else {
break; break;
@ -230,6 +206,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_group == null) return Container();
final group = _group!;
return GestureDetector( return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold( child: Scaffold(
@ -237,12 +215,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
title: GestureDetector( title: GestureDetector(
onTap: () async { onTap: () async {
if (group.isDirectChat) { if (group.isDirectChat) {
final member = final member = await twonlyDB.groupsDao.getAllGroupMembers(
await twonlyDB.groupsDao.getAllGroupMembers(group.groupId); group.groupId,
);
if (!context.mounted) return; if (!context.mounted) return;
if (member.isEmpty) return; if (member.isEmpty) return;
await context await context.push(
.push(Routes.profileContact(member.first.contactId)); Routes.profileContact(member.first.contactId),
);
} else { } else {
await context.push(Routes.profileGroup(group.groupId)); await context.push(Routes.profileGroup(group.groupId));
} }
@ -372,3 +352,32 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
); );
} }
} }
Color getMessageColor(Message message) {
return (message.senderId == null)
? const Color.fromARGB(255, 58, 136, 102)
: const Color.fromARGB(233, 68, 137, 255);
}
class ChatItem {
const ChatItem._({
this.message,
this.date,
this.groupAction,
});
factory ChatItem.date(DateTime date) {
return ChatItem._(date: date);
}
factory ChatItem.message(Message message) {
return ChatItem._(message: message);
}
factory ChatItem.groupAction(GroupHistory groupAction) {
return ChatItem._(groupAction: groupAction);
}
final GroupHistory? groupAction;
final Message? message;
final DateTime? date;
bool get isMessage => message != null;
bool get isDate => date != null;
bool get isGroupAction => groupAction != null;
}

View file

@ -102,8 +102,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
} }
Future<void> asyncLoadNextMedia(bool firstRun) async { Future<void> asyncLoadNextMedia(bool firstRun) async {
final messages = final messages = twonlyDB.messagesDao.watchMediaNotOpened(
twonlyDB.messagesDao.watchMediaNotOpened(widget.group.groupId); widget.group.groupId,
);
_subscription = messages.listen((messages) async { _subscription = messages.listen((messages) async {
for (final msg in messages) { for (final msg in messages) {
@ -121,8 +122,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
/// If the messages was already there just replace it and go to the next... /// If the messages was already there just replace it and go to the next...
final index = final index = allMediaFiles.indexWhere(
allMediaFiles.indexWhere((m) => m.messageId == msg.messageId); (m) => m.messageId == msg.messageId,
);
if (index >= 1) { if (index >= 1) {
allMediaFiles[index] = msg; allMediaFiles[index] = msg;
@ -160,7 +162,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
if (group != null && if (group != null &&
group.draftMessage != null && group.draftMessage != null &&
group.draftMessage != '') { group.draftMessage != '') {
context.replace(Routes.chatsMessages, extra: group); context.replace(Routes.chatsMessages(group.groupId));
} else { } else {
Navigator.pop(context); Navigator.pop(context);
} }
@ -190,8 +192,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
unawaited(flutterLocalNotificationsPlugin.cancelAll()); unawaited(flutterLocalNotificationsPlugin.cancelAll());
final stream = final stream = twonlyDB.mediaFilesDao.watchMedia(
twonlyDB.mediaFilesDao.watchMedia(allMediaFiles.first.mediaId!); allMediaFiles.first.mediaId!,
);
var downloadTriggered = false; var downloadTriggered = false;
@ -204,8 +207,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
}); });
if (!downloadTriggered) { if (!downloadTriggered) {
downloadTriggered = true; downloadTriggered = true;
final mediaFile = await twonlyDB.mediaFilesDao final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
.getMediaFileById(allMediaFiles.first.mediaId!); allMediaFiles.first.mediaId!,
);
if (mediaFile == null) return; if (mediaFile == null) return;
await startDownloadMedia(mediaFile, true); await startDownloadMedia(mediaFile, true);
unawaited(tryDownloadAllMediaFiles(force: true)); unawaited(tryDownloadAllMediaFiles(force: true));
@ -226,8 +230,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
setState(() { setState(() {
_showDownloadingLoader = false; _showDownloadingLoader = false;
}); });
final currentMediaLocal = final currentMediaLocal = await MediaFileService.fromMediaId(
await MediaFileService.fromMediaId(allMediaFiles.first.mediaId!); allMediaFiles.first.mediaId!,
);
if (currentMediaLocal == null || !mounted) return; if (currentMediaLocal == null || !mounted) return;
if (currentMediaLocal.mediaFile.requiresAuthentication) { if (currentMediaLocal.mediaFile.requiresAuthentication) {
@ -259,8 +264,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
}); });
if (!widget.group.isDirectChat) { if (!widget.group.isDirectChat) {
final sender = final sender = await twonlyDB.contactsDao.getContactById(
await twonlyDB.contactsDao.getContactById(currentMessage!.senderId!); currentMessage!.senderId!,
);
if (sender != null) { if (sender != null) {
_currentMediaSender = _currentMediaSender =
'${getContactDisplayName(sender)} (${widget.group.groupName})'; '${getContactDisplayName(sender)} (${widget.group.groupName})';
@ -285,24 +291,29 @@ class _MediaViewerViewState extends State<MediaViewerView> {
await videoController?.setLooping( await videoController?.setLooping(
currentMediaLocal.mediaFile.displayLimitInMilliseconds == null, currentMediaLocal.mediaFile.displayLimitInMilliseconds == null,
); );
await videoController?.initialize().then((_) { await videoController
?.initialize()
.then((_) {
if (videoController == null) return; if (videoController == null) return;
videoController?.play(); videoController?.play();
videoController?.addListener(() { videoController?.addListener(() {
setState(() { setState(() {
progress = 1 - progress =
1 -
videoController!.value.position.inSeconds / videoController!.value.position.inSeconds /
videoController!.value.duration.inSeconds; videoController!.value.duration.inSeconds;
}); });
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) { if (currentMediaLocal.mediaFile.displayLimitInMilliseconds !=
null) {
if (videoController?.value.position == if (videoController?.value.position ==
videoController?.value.duration) { videoController?.value.duration) {
nextMediaOrExit(); nextMediaOrExit();
} }
} }
}); });
// ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler })
}).catchError(Log.error); // ignore: argument_type_not_assignable_to_error_handler, invalid_return_type_for_catch_error
.catchError(Log.error);
} else { } else {
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) { if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) {
canBeSeenUntil = clock.now().add( canBeSeenUntil = clock.now().add(
@ -434,8 +445,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
height: 8, height: 8,
child: Center( child: Center(
child: EmojiAnimation( child: EmojiAnimation(
emoji: emoji: EmojiAnimation.animatedIcons.keys
EmojiAnimation.animatedIcons.keys.toList()[index], .toList()[index],
), ),
), ),
); );

View file

@ -35,8 +35,9 @@ class _StartNewChatView extends State<StartNewChatView> {
void initState() { void initState() {
super.initState(); super.initState();
contactSub = contactSub = twonlyDB.contactsDao.watchAllAcceptedContacts().listen((
twonlyDB.contactsDao.watchAllAcceptedContacts().listen((update) async { update,
) async {
update.sort( update.sort(
(a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)), (a, b) => getContactDisplayName(a).compareTo(getContactDisplayName(b)),
); );
@ -46,8 +47,9 @@ class _StartNewChatView extends State<StartNewChatView> {
await filterUsers(); await filterUsers();
}); });
allNonDirectGroupsSub = allNonDirectGroupsSub = twonlyDB.groupsDao
twonlyDB.groupsDao.watchGroupsForStartNewChat().listen((update) async { .watchGroupsForStartNewChat()
.listen((update) async {
setState(() { setState(() {
allNonDirectGroups = update; allNonDirectGroups = update;
}); });
@ -72,16 +74,16 @@ class _StartNewChatView extends State<StartNewChatView> {
} }
final usersFiltered = allContacts final usersFiltered = allContacts
.where( .where(
(user) => getContactDisplayName(user) (user) => getContactDisplayName(
.toLowerCase() user,
.contains(searchUserName.value.text.toLowerCase()), ).toLowerCase().contains(searchUserName.value.text.toLowerCase()),
) )
.toList(); .toList();
final groupsFiltered = allNonDirectGroups final groupsFiltered = allNonDirectGroups
.where( .where(
(g) => g.groupName (g) => g.groupName.toLowerCase().contains(
.toLowerCase() searchUserName.value.text.toLowerCase(),
.contains(searchUserName.value.text.toLowerCase()), ),
) )
.toList(); .toList();
setState(() { setState(() {
@ -108,7 +110,7 @@ class _StartNewChatView extends State<StartNewChatView> {
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) { builder: (context) {
return ChatMessagesView(directChat!); return ChatMessagesView(directChat!.groupId);
}, },
), ),
); );
@ -119,7 +121,7 @@ class _StartNewChatView extends State<StartNewChatView> {
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) { builder: (context) {
return ChatMessagesView(group); return ChatMessagesView(group.groupId);
}, },
), ),
); );
@ -133,8 +135,12 @@ class _StartNewChatView extends State<StartNewChatView> {
), ),
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
padding: padding: const EdgeInsets.only(
const EdgeInsets.only(bottom: 40, left: 10, top: 20, right: 10), bottom: 40,
left: 10,
top: 20,
right: 10,
),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
@ -167,8 +173,9 @@ class _StartNewChatView extends State<StartNewChatView> {
size: 13, size: 13,
), ),
), ),
onTap: () => context onTap: () => context.push(
.push(Routes.groupCreateSelectMember(null)), Routes.groupCreateSelectMember(null),
),
); );
} }
if (i == 1) { if (i == 1) {

View file

@ -46,10 +46,7 @@ class GroupContextMenu extends StatelessWidget {
), ),
ContextMenuItem( ContextMenuItem(
title: context.lang.contextMenuOpenChat, title: context.lang.contextMenuOpenChat,
onTap: () => context.push( onTap: () => context.push(Routes.chatsMessages(group.groupId)),
Routes.chatsMessages,
extra: group,
),
icon: FontAwesomeIcons.comments, icon: FontAwesomeIcons.comments,
), ),
if (!group.archived) if (!group.archived)

View file

@ -36,8 +36,9 @@ class _ContactViewState extends State<ContactView> {
@override @override
void initState() { void initState() {
_contactSub = _contactSub = twonlyDB.contactsDao.watchContact(widget.userId).listen((
twonlyDB.contactsDao.watchContact(widget.userId).listen((update) { update,
) {
setState(() { setState(() {
_contact = update; _contact = update;
}); });
@ -81,8 +82,9 @@ class _ContactViewState extends State<ContactView> {
final remove = await showAlertDialog( final remove = await showAlertDialog(
context, context,
context.lang context.lang.contactRemoveTitle(
.contactRemoveTitle(getContactDisplayName(contact, maxLength: 20)), getContactDisplayName(contact, maxLength: 20),
),
context.lang.contactRemoveBody, context.lang.contactRemoveBody,
); );
if (remove) { if (remove) {
@ -177,13 +179,11 @@ class _ContactViewState extends State<ContactView> {
icon: FontAwesomeIcons.solidComments, icon: FontAwesomeIcons.solidComments,
text: context.lang.contactViewMessage, text: context.lang.contactViewMessage,
onTap: () async { onTap: () async {
final group = final group = await twonlyDB.groupsDao.getDirectChat(
await twonlyDB.groupsDao.getDirectChat(contact.userId); contact.userId,
if (group != null && context.mounted) {
await context.push(
Routes.chatsMessages,
extra: group,
); );
if (group != null && context.mounted) {
await context.push(Routes.chatsMessages(group.groupId));
} }
}, },
), ),
@ -196,8 +196,10 @@ class _ContactViewState extends State<ContactView> {
if (context.mounted && nickName != null && nickName != '') { if (context.mounted && nickName != null && nickName != '') {
final update = ContactsCompanion(nickName: Value(nickName)); final update = ContactsCompanion(nickName: Value(nickName));
await twonlyDB.contactsDao await twonlyDB.contactsDao.updateContact(
.updateContact(contact.userId, update); contact.userId,
update,
);
} }
}, },
), ),
@ -247,8 +249,9 @@ Future<String?> showNicknameChangeDialog(
BuildContext context, BuildContext context,
Contact contact, Contact contact,
) { ) {
final controller = final controller = TextEditingController(
TextEditingController(text: getContactDisplayName(contact)); text: getContactDisplayName(contact),
);
return showDialog<String>( return showDialog<String>(
context: context, context: context,
@ -258,8 +261,9 @@ Future<String?> showNicknameChangeDialog(
content: TextField( content: TextField(
controller: controller, controller: controller,
autofocus: true, autofocus: true,
decoration: decoration: InputDecoration(
InputDecoration(hintText: context.lang.contactNicknameNew), hintText: context.lang.contactNicknameNew,
),
), ),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(
@ -271,8 +275,9 @@ Future<String?> showNicknameChangeDialog(
TextButton( TextButton(
child: Text(context.lang.ok), child: Text(context.lang.ok),
onPressed: () { onPressed: () {
Navigator.of(context) Navigator.of(
.pop(controller.text); // Return the input text context,
).pop(controller.text); // Return the input text
}, },
), ),
], ],
@ -291,8 +296,9 @@ Future<String?> showReportDialog(
context: context, context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: title: Text(
Text(context.lang.reportUserTitle(getContactDisplayName(contact))), context.lang.reportUserTitle(getContactDisplayName(contact)),
),
content: TextField( content: TextField(
controller: controller, controller: controller,
autofocus: true, autofocus: true,

View file

@ -124,14 +124,15 @@ class GroupMemberContextMenu extends StatelessWidget {
ContextMenuItem( ContextMenuItem(
title: context.lang.contextMenuOpenChat, title: context.lang.contextMenuOpenChat,
onTap: () async { onTap: () async {
final directChat = final directChat = await twonlyDB.groupsDao.getDirectChat(
await twonlyDB.groupsDao.getDirectChat(contact.userId); contact.userId,
);
if (directChat == null) { if (directChat == null) {
// create // create
return; return;
} }
if (!context.mounted) return; if (!context.mounted) return;
await context.push(Routes.chatsMessages, extra: directChat); await context.push(Routes.chatsMessages(directChat.groupId));
}, },
icon: FontAwesomeIcons.message, icon: FontAwesomeIcons.message,
), ),

View file

@ -13,26 +13,32 @@ 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';
class NotificationView extends StatelessWidget { class NotificationView extends StatefulWidget {
const NotificationView({super.key}); const NotificationView({super.key});
@override @override
Widget build(BuildContext context) { State<NotificationView> createState() => _NotificationViewState();
return Scaffold( }
appBar: AppBar(
title: Text(context.lang.settingsNotification), class _NotificationViewState extends State<NotificationView> {
), bool _isLoadingTroubleshooting = false;
body: ListView( bool _isLoadingReset = false;
children: [ bool _troubleshootingDidRun = false;
ListTile(
title: Text(context.lang.settingsNotifyTroubleshooting), Future<void> _troubleshooting() async {
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc), setState(() {
onTap: () async { _isLoadingTroubleshooting = true;
await initFCMAfterAuthenticated(); });
final storedToken = await (const FlutterSecureStorage()
.read(key: SecureStorageKeys.googleFcm)); await initFCMAfterAuthenticated(force: true);
final storedToken = await (const FlutterSecureStorage().read(
key: SecureStorageKeys.googleFcm,
));
await setupNotificationWithUsers(force: true); await setupNotificationWithUsers(force: true);
if (!context.mounted) return;
if (!mounted) return;
if (storedToken == null) { if (storedToken == null) {
final platform = Platform.isAndroid ? "Google's" : "Apple's"; final platform = Platform.isAndroid ? "Google's" : "Apple's";
@ -64,9 +70,66 @@ class NotificationView extends StatelessWidget {
pushData, pushData,
); );
} }
_troubleshootingDidRun = true;
} }
} }
}, setState(() {
_isLoadingTroubleshooting = false;
});
}
Future<void> resetTokens() async {
setState(() {
_isLoadingReset = true;
});
await resetFCMTokens();
if (!mounted) return;
await showAlertDialog(
context,
context.lang.settingsNotifyResetTitleReset,
context.lang.settingsNotifyResetTitleResetDesc,
);
setState(() {
_isLoadingReset = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(context.lang.settingsNotification),
),
body: ListView(
children: [
ListTile(
title: Text(context.lang.settingsNotifyTroubleshooting),
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc),
trailing: _isLoadingTroubleshooting
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: null,
onTap: _isLoadingTroubleshooting ? null : _troubleshooting,
),
if (_troubleshootingDidRun)
ListTile(
title: Text(context.lang.settingsNotifyResetTitle),
subtitle: Text(context.lang.settingsNotifyResetTitleSubtitle),
trailing: _isLoadingReset
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: null,
onTap: _isLoadingReset ? null : resetTokens,
), ),
], ],
), ),

View file

@ -13,10 +13,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _flutterfire_internals name: _flutterfire_internals
sha256: afe15ce18a287d2f89da95566e62892df339b1936bbe9b83587df45b944ee72a sha256: f698de6eb8a0dd7a9a931bbfe13568e8b77e702eb2deb13dd83480c5373e7746
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.67" version: "1.3.68"
adaptive_number: adaptive_number:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
@ -84,10 +84,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.13.0" version: "2.13.1"
audio_waveforms: audio_waveforms:
dependency: "direct main" dependency: "direct main"
description: description:
@ -124,18 +124,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.4" version: "4.0.5"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
@ -148,10 +148,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "39ad4ca8a2876779737c60e4228b4bcd35d4352ef7e14e47514093edc012c734" sha256: "521daf8d189deb79ba474e43a696b41c49fb3987818dbacf3308f1e03673a75e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.1" version: "2.13.1"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -164,10 +164,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.12.4" version: "8.12.5"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@ -196,27 +196,27 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: camera name: camera
sha256: "4142a19a38e388d3bab444227636610ba88982e36dff4552d5191a86f65dc437" sha256: "034c38cb8014d29698dcae6d20276688a1bf74e6487dfeb274d70ea05d5f7777"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.4" version: "0.12.0+1"
camera_android_camerax: camera_android_camerax:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
path: "packages/camera/camera_android_camerax" path: "packages/camera/camera_android_camerax"
ref: "43b87faec960306f98d767253b9bf2cee61be630" ref: e83fb3a27d4da2c37a3c8acbf2486283965b4f69
resolved-ref: "43b87faec960306f98d767253b9bf2cee61be630" resolved-ref: e83fb3a27d4da2c37a3c8acbf2486283965b4f69
url: "https://github.com/otsmr/flutter-packages.git" url: "https://github.com/otsmr/flutter-packages.git"
source: git source: git
version: "0.6.25+1" version: "0.7.1+2"
camera_avfoundation: camera_avfoundation:
dependency: transitive dependency: transitive
description: description:
name: camera_avfoundation name: camera_avfoundation
sha256: "11b4aee2f5e5e038982e152b4a342c749b414aa27857899d20f4323e94cb5f0b" sha256: "90e4cc3fde331581a3b2d35d83be41dbb7393af0ab857eb27b732174289cb96d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.23+2" version: "0.10.1"
camera_platform_interface: camera_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -301,18 +301,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: connectivity_plus name: connectivity_plus
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" sha256: b8fe52979ff12432ecf8f0abf6ff70410b1bb734be1c9e4f2f86807ad7166c79
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.1.0"
connectivity_plus_platform_interface: connectivity_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: connectivity_plus_platform_interface name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.1.0"
convert: convert:
dependency: "direct main" dependency: "direct main"
description: description:
@ -365,10 +365,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "6f6b30cba0301e7b38f32bdc9a6bdae6f5921a55f0a1eb9450e1e6515645dbb2" sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.6" version: "3.1.7"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@ -381,10 +381,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: "4df8babf73058181227e18b08e6ea3520cf5fc5d796888d33b7cb0f33f984b7c" sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.3.0" version: "12.4.0"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -504,54 +504,78 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.9.3+5" version: "0.9.3+5"
firebase_app_installations:
dependency: "direct main"
description:
name: firebase_app_installations
sha256: "9103cac19ec40561b49a023e8e583f007f77a499c2058f5a1d82ba480c2367d7"
url: "https://pub.dev"
source: hosted
version: "0.4.1"
firebase_app_installations_platform_interface:
dependency: transitive
description:
name: firebase_app_installations_platform_interface
sha256: "0811d37b91c992cc0c98200cca79652d0375a16c58b364a4db5601571c198ee5"
url: "https://pub.dev"
source: hosted
version: "0.1.4+67"
firebase_app_installations_web:
dependency: transitive
description:
name: firebase_app_installations_web
sha256: "02f2a96e85581bd1f78319b503dc92c80afa1527cbc299c7c921995b75595bbd"
url: "https://pub.dev"
source: hosted
version: "0.1.7+4"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
sha256: f0997fee80fbb6d2c658c5b88ae87ba1f9506b5b37126db64fc2e75d8e977fbb sha256: "2f988dab915efde3b3105268dbd69efce0e8570d767a218ccd914afd0c10c8cc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.5.0" version: "4.6.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_platform_interface name: firebase_core_platform_interface
sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.2" version: "6.0.3"
firebase_core_web: firebase_core_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
sha256: "856ca92bf2d75a63761286ab8e791bda3a85184c2b641764433b619647acfca6" sha256: "1399ab1f0ac3b503d8a9be64a4c997fc066bbf33f701f42866e5569f26205ebe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.5.0" version: "3.5.1"
firebase_messaging: firebase_messaging:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_messaging name: firebase_messaging
sha256: bd17823b70e629877904d384841cda72ed2cc197517404c0c90da5c0ba786a8c sha256: "8dc372085b1647f05e3ec1b8bc1dada87c0062f93b2a6976f620eb85edc44f97"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "16.1.2" version: "16.1.3"
firebase_messaging_platform_interface: firebase_messaging_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_platform_interface name: firebase_messaging_platform_interface
sha256: "550435235cc7d53683f32bf0762c28ef8cfc20a8d36318a033676ae09526d7fb" sha256: "6ea10f7df747542b17679d5939213c09163aab9c301b2f9b858cb55f38efdb54"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.7" version: "4.7.8"
firebase_messaging_web: firebase_messaging_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_messaging_web name: firebase_messaging_web
sha256: "6b1b93ed90309fbce91c219e3cd32aa831e8eccaf4a61f3afaea1625479275d2" sha256: "1f9798c8021ccf22b7e43e7fba81becd42252cb168228379fcabb7c2ef7dd638"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.3" version: "4.1.4"
fixnum: fixnum:
dependency: "direct main" dependency: "direct main"
description: description:
@ -727,10 +751,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.33" version: "2.0.34"
flutter_secure_storage: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@ -790,10 +814,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" sha256: "1ded017b39c8e15c8948ea855070a5ff8ff8b3d5e83f3446e02d6bb12add7ad9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.3" version: "2.2.4"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -848,10 +872,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: go_router name: go_router
sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896" sha256: "48fb2f42ad057476fa4b733cb95e9f9ea7b0b010bb349ea491dca7dbdb18ffc4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "17.1.0" version: "17.2.0"
google_mlkit_barcode_scanning: google_mlkit_barcode_scanning:
dependency: "direct main" dependency: "direct main"
description: description:
@ -917,10 +941,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: hooks name: hooks
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.2"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -973,10 +997,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image_picker_android name: image_picker_android
sha256: eda9b91b7e266d9041084a42d605a74937d996b87083395c5e47835916a86156 sha256: "66810af8e99b2657ee98e5c6f02064f69bb63f7a70e343937f70946c5f8c6622"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.8.13+14" version: "0.8.13+16"
image_picker_for_web: image_picker_for_web:
dependency: transitive dependency: transitive
description: description:
@ -1037,10 +1061,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: in_app_purchase_android name: in_app_purchase_android
sha256: abb254ae159a5a9d4f867795ecb076864faeba59ce015ab81d4cca380f23df45 sha256: "634bee4734b17fe55f370f0ac07a22431a9666e0f3a870c6d20350856e8bbf71"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.0+8" version: "0.4.0+10"
in_app_purchase_platform_interface: in_app_purchase_platform_interface:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -1053,10 +1077,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: in_app_purchase_storekit name: in_app_purchase_storekit
sha256: "2f1a1db44798158076ced07d401b349880dd24a29c7c50a1b1a0de230b7f2f62" sha256: "1d512809edd9f12ff88fce4596a13a18134e2499013f4d6a8894b04699363c93"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.8" version: "0.4.8+1"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1100,10 +1124,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
sha256: "44729f5c45748e6748f6b9a57ab8f7e4336edc8ae41fc295070e3814e616a6c0" sha256: fbcf404b03520e6e795f6b9b39badb2b788407dfc0a50cf39158a6ae1ca78925
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.13.0" version: "6.13.1"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -1155,10 +1179,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: local_auth_android name: local_auth_android
sha256: dc9663a7bc8ac33d7d988e63901974f63d527ebef260eabd19c479447cc9c911 sha256: b41970749c2d43791790724b76917eeee1e90de76e6b0eec3edca03a329bf44c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.5" version: "2.0.7"
local_auth_darwin: local_auth_darwin:
dependency: transitive dependency: transitive
description: description:
@ -1241,10 +1265,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: native_toolchain_c name: native_toolchain_c
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.4" version: "0.17.6"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -1303,10 +1327,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.0" version: "9.0.1"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1343,10 +1367,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.22" version: "2.2.23"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -1485,10 +1509,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: pro_video_editor name: pro_video_editor
sha256: "5aa37aed1399333a3ac4b78ce00c7dcba77c5e407b6420960bba43751895fa22" sha256: cfed1424b3ca3d5981cc81efdd20b844c995c0ad2818e185eb5bc06a8674f728
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.2" version: "1.14.2"
protobuf: protobuf:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1570,26 +1594,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sentry name: sentry
sha256: "605ad1f6f1ae5b72018cbe8fc20f490fa3bd53e58882e5579566776030d8c8c1" sha256: "288aee3d35f252ac0dc3a4b0accbbe7212fa2867604027f2cc5bc65334afd743"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.14.0" version: "9.16.0"
sentry_flutter: sentry_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: sentry_flutter name: sentry_flutter
sha256: "7fd0fb80050c1f6a77ae185bda997a76d384326d6777cf5137a6c38952c4ac7d" sha256: f9e87d5895cc437902aa2b081727ee7e46524fe7cc2e1910f535480a3eeb8bed
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.14.0" version: "9.16.0"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840" sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "12.0.1" version: "12.0.2"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -1602,18 +1626,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4" version: "2.5.5"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: "8374d6200ab33ac99031a852eba4c8eb2170c4bf20778b3e2c9eccb45384fb41" sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.21" version: "2.4.23"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@ -1634,10 +1658,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
@ -1679,18 +1703,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17" sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.0" version: "4.2.2"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "4a85e90b50694e652075cbe4575665539d253e6ec10e46e76b45368ab5e3caae" sha256: "1d3b229b2934034fb2e691fbb3d53e0f75a4af7b1407f88425ed8f209bcb1b8f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.10" version: "1.3.11"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -1711,10 +1735,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite_android name: sqflite_android
sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2+2" version: "2.4.2+3"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
@ -1751,10 +1775,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqlite3_flutter_libs name: sqlite3_flutter_libs
sha256: "1e800ebe7f85a80a66adacaa6febe4d5f4d8b75f244e9838a27cb2ffc7aec08d" sha256: eeb9e3a45207649076b808f8a5a74d68770d0b7f26ccef6d5f43106eee5375ad
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.41" version: "0.5.42"
sqlparser: sqlparser:
dependency: transitive dependency: transitive
description: description:
@ -1847,10 +1871,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.28" version: "6.3.29"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -1911,10 +1935,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: vector_graphics name: vector_graphics
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 sha256: "81da85e9ca8885ade47f9685b953cb098970d11be4821ac765580a6607ea4373"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.19" version: "1.1.21"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
@ -1951,26 +1975,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: video_player name: video_player
sha256: "08bfba72e311d48219acad4e191b1f9c27ff8cf928f2c7234874592d9c9d7341" sha256: "48a7bdaa38a3d50ec10c78627abdbfad863fdf6f0d6e08c7c3c040cfd80ae36f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.11.1"
video_player_android: video_player_android:
dependency: transitive dependency: transitive
description: description:
name: video_player_android name: video_player_android
sha256: "9862c67c4661c98f30fe707bc1a4f97d6a0faa76784f485d282668e4651a7ac3" sha256: "877a6c7ba772456077d7bfd71314629b3fe2b73733ce503fc77c3314d43a0ca0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.9.4" version: "2.9.5"
video_player_avfoundation: video_player_avfoundation:
dependency: transitive dependency: transitive
description: description:
name: video_player_avfoundation name: video_player_avfoundation
sha256: f93b93a3baa12ca0ff7d00ca8bc60c1ecd96865568a01ff0c18a99853ee201a5 sha256: af0e5b8a7a4876fb37e7cc8cb2a011e82bb3ecfa45844ef672e32cb14a1f259e
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.9.3" version: "2.9.4"
video_player_platform_interface: video_player_platform_interface:
dependency: transitive dependency: transitive
description: description:

View file

@ -30,7 +30,7 @@ dependencies:
# Trusted publisher flutter.dev # Trusted publisher flutter.dev
camera: ^0.11.2 camera: ^0.12.0+1
flutter_svg: ^2.0.17 flutter_svg: ^2.0.17
image_picker: ^1.1.2 image_picker: ^1.1.2
local_auth: ^3.0.0 local_auth: ^3.0.0
@ -54,6 +54,7 @@ dependencies:
# Trustworthy publishers # Trustworthy publishers
firebase_core: ^4.3.0 # firebase.google.com firebase_core: ^4.3.0 # firebase.google.com
firebase_messaging: ^16.1.0 # firebase.google.com firebase_messaging: ^16.1.0 # firebase.google.com
firebase_app_installations: ^0.4.1 # firebase.google.com
json_annotation: ^4.9.0 # google.dev json_annotation: ^4.9.0 # google.dev
protobuf: ^4.0.0 # google.dev protobuf: ^4.0.0 # google.dev
scrollable_positioned_list: ^0.3.8 # google.dev scrollable_positioned_list: ^0.3.8 # google.dev
@ -152,7 +153,7 @@ dependency_overrides:
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: 43b87faec960306f98d767253b9bf2cee61be630 ref: e83fb3a27d4da2c37a3c8acbf2486283965b4f69
emoji_picker_flutter: emoji_picker_flutter:
# Fixes the issue with recent emojis (solved by https://github.com/Fintasys/emoji_picker_flutter/pull/238) # Fixes the issue with recent emojis (solved by https://github.com/Fintasys/emoji_picker_flutter/pull/238)
# Using override until this gets merged. # Using override until this gets merged.