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,46 +42,69 @@ 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
await updateUserdata((u) { .listen((fcmToken) async {
u.updateFCMToken = true; Log.info('Got new FCM TOKEN.');
return u; await storage.write(
}); key: SecureStorageKeys.googleFcm,
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken); value: fcmToken,
}).onError((err) { );
Log.error('could not listen on token refresh'); await updateUserdata((u) {
}); u.updateFCMToken = true;
return u;
});
})
.onError((err) {
Log.error('could not listen on token refresh');
});
} catch (e) { } catch (e) {
Log.error('could not load fcm token: $e'); Log.error('could not load fcm token: $e');
} }
} }
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 {
await Firebase.initializeApp( await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform, options: DefaultFirebaseOptions.currentPlatform,

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,25 +343,28 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
await deInitVolumeControl(); await deInitVolumeControl();
if (!mounted) return true; if (!mounted) return true;
final shouldReturn = await Navigator.push( final shouldReturn =
context, await Navigator.push(
PageRouteBuilder( context,
opaque: false, PageRouteBuilder(
pageBuilder: (context, a1, a2) => ShareImageEditorView( opaque: false,
screenshotImage: screenshotImage, pageBuilder: (context, a1, a2) => ShareImageEditorView(
sharedFromGallery: sharedFromGallery, screenshotImage: screenshotImage,
sendToGroup: widget.sendToGroup, sharedFromGallery: sharedFromGallery,
mediaFileService: mediaFileService, sendToGroup: widget.sendToGroup,
mainCameraController: mc, mediaFileService: mediaFileService,
previewLink: mc.sharedLinkForPreview, mainCameraController: mc,
), previewLink: mc.sharedLinkForPreview,
transitionsBuilder: (context, animation, secondaryAnimation, child) { ),
return child; transitionsBuilder:
}, (context, animation, secondaryAnimation, child) {
transitionDuration: Duration.zero, return child;
reverseTransitionDuration: Duration.zero, },
), transitionDuration: Duration.zero,
) as bool?; reverseTransitionDuration: Duration.zero,
),
)
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 =
// ignore: avoid_dynamic_calls (_baseScaleFactor +
(_basePanY - (details.localPosition.dy as double)) / 30) // ignore: avoid_dynamic_calls
.clamp(1, mc.selectedCameraDetails.maxAvailableZoom); (_basePanY - (details.localPosition.dy as double)) / 30)
.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(() {});
@ -739,8 +762,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
child: FaIcon( child: FaIcon(
mc.isSelectingFaceFilters mc.isSelectingFaceFilters
? mc.currentFilterType.index == 1 ? mc.currentFilterType.index == 1
? FontAwesomeIcons.xmark ? FontAwesomeIcons.xmark
: FontAwesomeIcons.arrowLeft : FontAwesomeIcons.arrowLeft
: FontAwesomeIcons.photoFilm, : FontAwesomeIcons.photoFilm,
color: Colors.white, color: Colors.white,
size: 25, size: 25,
@ -785,13 +808,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
child: FaIcon( child: FaIcon(
mc.isSelectingFaceFilters mc.isSelectingFaceFilters
? mc.currentFilterType.index == ? mc.currentFilterType.index ==
FaceFilterType FaceFilterType
.values.length - .values
1 .length -
? FontAwesomeIcons.xmark 1
: FontAwesomeIcons.arrowRight ? FontAwesomeIcons.xmark
: FontAwesomeIcons.arrowRight
: FontAwesomeIcons : FontAwesomeIcons
.faceGrinTongueSquint, .faceGrinTongueSquint,
color: Colors.white, color: Colors.white,
size: 25, size: 25,
), ),
@ -843,64 +867,64 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
children: [ children: [
...widget.mainCameraController.scannedNewProfiles.values ...widget.mainCameraController.scannedNewProfiles.values
.map( .map(
(c) { (c) {
if (c.isLoading) return Container(); if (c.isLoading) return Container();
return GestureDetector( return GestureDetector(
onTap: () async { onTap: () async {
c.isLoading = true; c.isLoading = true;
widget.mainCameraController.setState(); widget.mainCameraController.setState();
if (await addNewContactFromPublicProfile( if (await addNewContactFromPublicProfile(
c.profile, c.profile,
) && ) &&
context.mounted) { context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text(
context.lang.requestedUserToastText( context.lang.requestedUserToastText(
c.profile.username, c.profile.username,
),
),
duration: const Duration(seconds: 8),
), ),
), );
duration: const Duration(seconds: 8), }
},
child: Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: context.color.surfaceContainer,
), ),
); child: Row(
} children: [
Text(c.profile.username),
Expanded(child: Container()),
if (c.isLoading)
const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
else
ColoredBox(
color: Colors.transparent,
child: FaIcon(
FontAwesomeIcons.userPlus,
color: isDarkMode(context)
? Colors.white
: Colors.black,
size: 17,
),
),
],
),
),
);
}, },
child: Container( ),
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: context.color.surfaceContainer,
),
child: Row(
children: [
Text(c.profile.username),
Expanded(child: Container()),
if (c.isLoading)
const SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
else
ColoredBox(
color: Colors.transparent,
child: FaIcon(
FontAwesomeIcons.userPlus,
color: isDarkMode(context)
? Colors.white
: Colors.black,
size: 17,
),
),
],
),
),
);
},
),
...widget.mainCameraController.contactsVerified.values.map( ...widget.mainCameraController.contactsVerified.values.map(
(c) { (c) {
return Container( return Container(
@ -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

@ -64,41 +64,43 @@ class _UserListItem extends State<GroupListItem> {
_lastMessageStream = twonlyDB.messagesDao _lastMessageStream = twonlyDB.messagesDao
.watchLastMessage(widget.group.groupId) .watchLastMessage(widget.group.groupId)
.listen((update) { .listen((update) {
protectUpdateState.protect(() async { protectUpdateState.protect(() async {
await updateState(update, _messagesNotOpened); await updateState(update, _messagesNotOpened);
}); });
}); });
_lastReactionStream = twonlyDB.reactionsDao _lastReactionStream = twonlyDB.reactionsDao
.watchLastReactions(widget.group.groupId) .watchLastReactions(widget.group.groupId)
.listen((update) { .listen((update) {
setState(() { setState(() {
_lastReaction = update; _lastReaction = update;
}); });
// protectUpdateState.protect(() async { // protectUpdateState.protect(() async {
// await updateState(lastMessage, update, messagesNotOpened); // await updateState(lastMessage, update, messagesNotOpened);
// }); // });
}); });
_messagesNotOpenedStream = twonlyDB.messagesDao _messagesNotOpenedStream = twonlyDB.messagesDao
.watchMessageNotOpened(widget.group.groupId) .watchMessageNotOpened(widget.group.groupId)
.listen((update) { .listen((update) {
protectUpdateState.protect(() async { protectUpdateState.protect(() async {
await updateState(_lastMessage, update); await updateState(_lastMessage, update);
}); });
}); });
_lastMediaFilesStream = _lastMediaFilesStream = twonlyDB.mediaFilesDao
twonlyDB.mediaFilesDao.watchNewestMediaFiles().listen((mediaFiles) { .watchNewestMediaFiles()
for (final mediaFile in mediaFiles) { .listen((mediaFiles) {
final index = _previewMediaFiles for (final mediaFile in mediaFiles) {
.indexWhere((t) => t.mediaId == mediaFile.mediaId); final index = _previewMediaFiles.indexWhere(
if (index >= 0) { (t) => t.mediaId == mediaFile.mediaId,
_previewMediaFiles[index] = mediaFile; );
} if (index >= 0) {
} _previewMediaFiles[index] = mediaFile;
setState(() {}); }
}); }
setState(() {});
});
} }
Mutex protectUpdateState = Mutex(); Mutex protectUpdateState = Mutex();
@ -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
@ -206,18 +208,18 @@ class _UserListItem extends State<GroupListItem> {
), ),
subtitle: (_currentMessage == null) subtitle: (_currentMessage == null)
? (widget.group.totalMediaCounter == 0) ? (widget.group.totalMediaCounter == 0)
? Text(context.lang.chatsTapToSend) ? Text(context.lang.chatsTapToSend)
: 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,
), ),
], ],
) )
: Row( : Row(
children: [ children: [
MessageSendStateIcon( MessageSendStateIcon(
@ -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 {
extra: widget.group, context.push(
), Routes.chatsCameraSendTo,
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,30 +73,34 @@ 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;
});
protectMessageUpdating.protect(() async {
if (groupActionsSub == null && !newGroup.isDirectChat) {
final actionsStream = twonlyDB.groupsDao.watchGroupActions(
newGroup.groupId,
);
groupActionsSub = actionsStream.listen((update) async {
groupActions = update;
await setMessages(allMessages, update);
});
final contactsStream = twonlyDB.contactsDao.watchAllContacts();
contactSub = contactsStream.listen((contacts) {
for (final contact in contacts) {
userIdToContact[contact.userId] = contact;
}
});
}
}); });
}); });
if (!widget.group.isDirectChat) { final msgStream = twonlyDB.messagesDao.watchByGroupId(widget.groupId);
final actionsStream = twonlyDB.groupsDao.watchGroupActions(group.groupId);
groupActionsSub = actionsStream.listen((update) async {
groupActions = update;
await setMessages(allMessages, update);
});
final contactsStream = twonlyDB.contactsDao.watchAllContacts();
contactSub = contactsStream.listen((contacts) {
for (final contact in contacts) {
userIdToContact[contact.userId] = contact;
}
});
}
final msgStream = twonlyDB.messagesDao.watchByGroupId(group.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,32 +291,37 @@ 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
if (videoController == null) return; ?.initialize()
videoController?.play(); .then((_) {
videoController?.addListener(() { if (videoController == null) return;
setState(() { videoController?.play();
progress = 1 - videoController?.addListener(() {
videoController!.value.position.inSeconds / setState(() {
videoController!.value.duration.inSeconds; progress =
}); 1 -
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) { videoController!.value.position.inSeconds /
if (videoController?.value.position == videoController!.value.duration.inSeconds;
videoController?.value.duration) { });
nextMediaOrExit(); if (currentMediaLocal.mediaFile.displayLimitInMilliseconds !=
} null) {
} if (videoController?.value.position ==
}); videoController?.value.duration) {
// ignore: invalid_return_type_for_catch_error, argument_type_not_assignable_to_error_handler nextMediaOrExit();
}).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(
Duration( Duration(
milliseconds: milliseconds:
currentMediaLocal.mediaFile.displayLimitInMilliseconds!, currentMediaLocal.mediaFile.displayLimitInMilliseconds!,
), ),
); );
timerRequired = true; timerRequired = true;
} }
} }
@ -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,13 +47,14 @@ class _StartNewChatView extends State<StartNewChatView> {
await filterUsers(); await filterUsers();
}); });
allNonDirectGroupsSub = allNonDirectGroupsSub = twonlyDB.groupsDao
twonlyDB.groupsDao.watchGroupsForStartNewChat().listen((update) async { .watchGroupsForStartNewChat()
setState(() { .listen((update) async {
allNonDirectGroups = update; setState(() {
}); allNonDirectGroups = update;
await filterUsers(); });
}); await filterUsers();
});
} }
@override @override
@ -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;
}); });
@ -45,8 +46,8 @@ class _ContactViewState extends State<ContactView> {
_groupMemberSub = twonlyDB.groupsDao _groupMemberSub = twonlyDB.groupsDao
.watchContactGroupMember(widget.userId) .watchContactGroupMember(widget.userId)
.listen((groups) async { .listen((groups) async {
_memberOfGroups = groups; _memberOfGroups = groups;
}); });
super.initState(); super.initState();
} }
@ -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) { if (group != null && context.mounted) {
await context.push( await context.push(Routes.chatsMessages(group.groupId));
Routes.chatsMessages,
extra: group,
);
} }
}, },
), ),
@ -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,9 +13,87 @@ 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
State<NotificationView> createState() => _NotificationViewState();
}
class _NotificationViewState extends State<NotificationView> {
bool _isLoadingTroubleshooting = false;
bool _isLoadingReset = false;
bool _troubleshootingDidRun = false;
Future<void> _troubleshooting() async {
setState(() {
_isLoadingTroubleshooting = true;
});
await initFCMAfterAuthenticated(force: true);
final storedToken = await (const FlutterSecureStorage().read(
key: SecureStorageKeys.googleFcm,
));
await setupNotificationWithUsers(force: true);
if (!mounted) return;
if (storedToken == null) {
final platform = Platform.isAndroid ? "Google's" : "Apple's";
await showAlertDialog(
context,
'Problem detected',
'twonly is not able to register your app to $platform push server infrastructure. For Android that can happen when you do not have the Google Play Services installed. If you theses installed and want to help us to fix the issue please send us your debug log in Settings > Help > Debug log.',
);
} else {
final run = await showAlertDialog(
context,
context.lang.settingsNotifyTroubleshootingNoProblem,
context.lang.settingsNotifyTroubleshootingNoProblemDesc,
);
if (run) {
final user = await getUser();
if (user != null) {
final pushData = await encryptPushNotification(
user.userId,
PushNotification(
messageId: uuid.v4(),
kind: PushKind.testNotification,
),
);
await apiService.sendTextMessage(
user.userId,
Uint8List(0),
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -27,47 +105,32 @@ class NotificationView extends StatelessWidget {
ListTile( ListTile(
title: Text(context.lang.settingsNotifyTroubleshooting), title: Text(context.lang.settingsNotifyTroubleshooting),
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc), subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc),
onTap: () async { trailing: _isLoadingTroubleshooting
await initFCMAfterAuthenticated(); ? const SizedBox(
final storedToken = await (const FlutterSecureStorage() width: 16,
.read(key: SecureStorageKeys.googleFcm)); height: 16,
await setupNotificationWithUsers(force: true); child: CircularProgressIndicator(
if (!context.mounted) return; strokeWidth: 2,
),
if (storedToken == null) { )
final platform = Platform.isAndroid ? "Google's" : "Apple's"; : null,
await showAlertDialog( onTap: _isLoadingTroubleshooting ? null : _troubleshooting,
context,
'Problem detected',
'twonly is not able to register your app to $platform push server infrastructure. For Android that can happen when you do not have the Google Play Services installed. If you theses installed and want to help us to fix the issue please send us your debug log in Settings > Help > Debug log.',
);
} else {
final run = await showAlertDialog(
context,
context.lang.settingsNotifyTroubleshootingNoProblem,
context.lang.settingsNotifyTroubleshootingNoProblemDesc,
);
if (run) {
final user = await getUser();
if (user != null) {
final pushData = await encryptPushNotification(
user.userId,
PushNotification(
messageId: uuid.v4(),
kind: PushKind.testNotification,
),
);
await apiService.sendTextMessage(
user.userId,
Uint8List(0),
pushData,
);
}
}
}
},
), ),
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.