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
## 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
- 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 chatsCameraSendTo = '/chats/camera_send_to';
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) =>
'/group/create/select_member${groupId == null ? '' : '/$groupId'}';
@ -53,5 +54,7 @@ class Routes {
'/settings/developer/retransmission_database';
static const String settingsDeveloperAutomatedTesting =
'/settings/developer/automated_testing';
static const String settingsDeveloperReduceFlames =
'/settings/developer/reduce_flames';
static const String settingsInvite = '/settings/invite';
}

View file

@ -733,9 +733,33 @@ abstract class AppLocalizations {
/// No description provided for @settingsNotifyTroubleshootingNoProblemDesc.
///
/// In en, this message translates to:
/// **'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.'**
/// **'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;
/// 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.
///
/// In en, this message translates to:

View file

@ -356,7 +356,22 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get settingsNotifyTroubleshootingNoProblemDesc =>
'Klicke auf OK, um eine Testbenachrichtigung zu erhalten. Wenn du auch nach 10 Minuten warten keine Nachricht erhältst, sende uns bitte dein Diagnoseprotokoll unter Einstellungen > Hilfe > Diagnoseprotokoll, damit wir uns das Problem ansehen können.';
'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
String get settingsHelp => 'Hilfe';

View file

@ -351,7 +351,22 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get settingsNotifyTroubleshootingNoProblemDesc =>
'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.';
'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
String get settingsHelp => 'Help';

View file

@ -351,7 +351,22 @@ class AppLocalizationsSv extends AppLocalizations {
@override
String get settingsNotifyTroubleshootingNoProblemDesc =>
'Press OK to receive a test notification. When you receive no message even after waiting for 10 minutes, please send us your debug log in Settings > Help > Debug log, so we can look at that issue.';
'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
String get settingsHelp => 'Help';

View file

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

View file

@ -30,6 +30,8 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) =>
..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt()
..requestedAudioPermission =
json['requestedAudioPermission'] as bool? ?? false
..videoStabilizationEnabled =
json['videoStabilizationEnabled'] as bool? ?? false
..showFeedbackShortcut = json['showFeedbackShortcut'] as bool? ?? true
..showShowImagePreviewWhenSending =
json['showShowImagePreviewWhenSending'] as bool? ?? false
@ -105,6 +107,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'defaultShowTime': instance.defaultShowTime,
'requestedAudioPermission': instance.requestedAudioPermission,
'videoStabilizationEnabled': instance.videoStabilizationEnabled,
'showFeedbackShortcut': instance.showFeedbackShortcut,
'showShowImagePreviewWhenSending': instance.showShowImagePreviewWhenSending,
'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/developer/automated_testing.view.dart';
import 'package:twonly/src/views/settings/developer/developer.view.dart';
import 'package:twonly/src/views/settings/developer/reduce_flames.view.dart';
import 'package:twonly/src/views/settings/developer/retransmission_data.view.dart';
import 'package:twonly/src/views/settings/help/changelog.view.dart';
import 'package:twonly/src/views/settings/help/contact_us.view.dart';
@ -84,10 +85,10 @@ final routerProvider = GoRouter(
},
),
GoRoute(
path: 'messages',
path: 'messages/:groupId',
builder: (context, state) {
final group = state.extra! as Group;
return ChatMessagesView(group);
final groupId = state.pathParameters['groupId']!;
return ChatMessagesView(groupId);
},
),
],
@ -280,6 +281,10 @@ final routerProvider = GoRouter(
path: 'automated_testing',
builder: (context, state) => const AutomatedTestingView(),
),
GoRoute(
path: 'reduce_flames',
builder: (context, state) => const ReduceFlamesView(),
),
],
),
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);
late EncryptedContent_Media_Type type;
@ -329,10 +337,11 @@ Future<void> _createUploadRequest(MediaFileService media) async {
Log.error(
'Could not generate ciphertext message for ${groupMember.contactId}',
);
continue;
}
final messageOnSuccess = TextMessage()
..body = cipherText!.$1
..body = cipherText.$1
..userId = Int64(groupMember.contactId);
if (cipherText.$2 != null) {

View file

@ -13,8 +13,9 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
final groups = await twonlyDB.groupsDao.getAllGroups();
if (groups.isEmpty) return;
final maxMessageCounter = groups.map((x) => x.totalMediaCounter).max;
final bestFriend =
groups.firstWhere((x) => x.totalMediaCounter == maxMessageCounter);
final bestFriend = groups.firstWhere(
(x) => x.totalMediaCounter == maxMessageCounter,
);
if (gUser.myBestFriendGroupId != bestFriend.groupId) {
await updateUserdata((user) {
@ -42,8 +43,9 @@ Future<void> syncFlameCounters({String? forceForGroup}) async {
EncryptedContent(
flameSync: EncryptedContent_FlameSync(
flameCounter: Int64(flameCounter),
lastFlameCounterChange:
Int64(group.lastFlameCounterChange!.millisecondsSinceEpoch),
lastFlameCounterChange: Int64(
group.lastFlameCounterChange!.millisecondsSinceEpoch,
),
bestFriend: group.groupId == bestFriend.groupId,
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
if (flameCounter >= maxFlameCounter ||
maxFlameCounterFrom == null ||
maxFlameCounterFrom
.isBefore(clock.now().subtract(const Duration(days: 5)))) {
maxFlameCounterFrom.isBefore(
clock.now().subtract(const Duration(days: 5)),
)) {
maxFlameCounter = flameCounter;
maxFlameCounterFrom = clock.now();
}
@ -172,6 +175,7 @@ bool isItPossibleToRestoreFlames(Group group) {
final flameCounter = getFlameCounterFromGroup(group);
return group.maxFlameCounter > 2 &&
flameCounter < group.maxFlameCounter &&
group.maxFlameCounterFrom!
.isAfter(clock.now().subtract(const Duration(days: 5)));
group.maxFlameCounterFrom!.isAfter(
clock.now().subtract(const Duration(days: 5)),
);
}

View file

@ -3,6 +3,7 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'package:firebase_app_installations/firebase_app_installations.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.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');
return;
}
Log.info('Loaded fcm token');
Log.info('Loaded FCM token.');
if (storedToken == null || fcmToken != storedToken) {
Log.info('Got new FCM TOKEN.');
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
await updateUserdata((u) {
u.updateFCMToken = true;
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) {
u.updateFCMToken = true;
return u;
});
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
}).onError((err) {
})
.onError((err) {
Log.error('could not listen on token refresh');
});
} catch (e) {
@ -64,21 +74,35 @@ Future<void> checkForTokenUpdates() async {
}
}
Future<void> initFCMAfterAuthenticated() async {
if (gUser.updateFCMToken) {
Future<void> initFCMAfterAuthenticated({bool force = false}) async {
if (gUser.updateFCMToken || force) {
const storage = FlutterSecureStorage();
final storedToken = await storage.read(key: SecureStorageKeys.googleFcm);
if (storedToken != null) {
final res = await apiService.updateFCMToken(storedToken);
if (res.isSuccess) {
Log.info('Uploaded new fmt token!');
Log.info('Uploaded new FCM token!');
await updateUserdata((u) {
u.updateFCMToken = false;
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 {

View file

@ -129,9 +129,9 @@ Future<void> cleanLogFile() async {
if (logFile.existsSync()) {
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 sink = logFile.openWrite()..writeAll(remaining, '\n');

View file

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

View file

@ -88,11 +88,13 @@ class _UserListItem extends State<GroupListItem> {
});
});
_lastMediaFilesStream =
twonlyDB.mediaFilesDao.watchNewestMediaFiles().listen((mediaFiles) {
_lastMediaFilesStream = twonlyDB.mediaFilesDao
.watchNewestMediaFiles()
.listen((mediaFiles) {
for (final mediaFile in mediaFiles) {
final index = _previewMediaFiles
.indexWhere((t) => t.mediaId == mediaFile.mediaId);
final index = _previewMediaFiles.indexWhere(
(t) => t.mediaId == mediaFile.mediaId,
);
if (index >= 0) {
_previewMediaFiles[index] = mediaFile;
}
@ -113,8 +115,9 @@ class _UserListItem extends State<GroupListItem> {
_previewMessages = [];
} else if (newMessagesNotOpened.isNotEmpty) {
// Filter for the preview non opened messages. First messages which where send but not yet opened by the other side.
final receivedMessages =
newMessagesNotOpened.where((x) => x.senderId != null).toList();
final receivedMessages = newMessagesNotOpened
.where((x) => x.senderId != null)
.toList();
if (receivedMessages.isNotEmpty) {
_previewMessages = receivedMessages;
@ -145,8 +148,9 @@ class _UserListItem extends State<GroupListItem> {
for (final message in _previewMessages) {
if (message.mediaId != null &&
!_previewMediaFiles.any((t) => t.mediaId == message.mediaId)) {
final mediaFile =
await twonlyDB.mediaFilesDao.getMediaFileById(message.mediaId!);
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
message.mediaId!,
);
if (mediaFile != null) {
_previewMediaFiles.add(mediaFile);
}
@ -171,8 +175,9 @@ class _UserListItem extends State<GroupListItem> {
final msgs = _previewMessages
.where((x) => x.type == MessageType.media.name)
.toList();
final mediaFile =
await twonlyDB.mediaFilesDao.getMediaFileById(msgs.first.mediaId!);
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
msgs.first.mediaId!,
);
if (mediaFile?.type != MediaType.audio) {
if (mediaFile?.downloadState == null) return;
if (mediaFile!.downloadState! == DownloadState.pending) {
@ -190,10 +195,7 @@ class _UserListItem extends State<GroupListItem> {
}
}
if (!mounted) return;
await context.push(
Routes.chatsMessages,
extra: widget.group,
);
await context.push(Routes.chatsMessages(widget.group.groupId));
}
@override
@ -239,8 +241,9 @@ class _UserListItem extends State<GroupListItem> {
leading: GestureDetector(
onTap: () async {
if (widget.group.isDirectChat) {
final contacts = await twonlyDB.groupsDao
.getGroupContact(widget.group.groupId);
final contacts = await twonlyDB.groupsDao.getGroupContact(
widget.group.groupId,
);
if (!context.mounted) return;
await context.push(Routes.profileContact(contacts.first.userId));
return;
@ -253,12 +256,16 @@ class _UserListItem extends State<GroupListItem> {
trailing: (widget.group.leftGroup)
? null
: IconButton(
onPressed: () => context.push(
_hasNonOpenedMediaFile
? Routes.chatsMessages
: Routes.chatsCameraSendTo,
onPressed: () {
if (_hasNonOpenedMediaFile) {
context.push(Routes.chatsMessages(widget.group.groupId));
} else {
context.push(
Routes.chatsCameraSendTo,
extra: widget.group,
),
);
}
},
icon: FaIcon(
_hasNonOpenedMediaFile
? 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/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.
class ChatMessagesView extends StatefulWidget {
const ChatMessagesView(this.group, {super.key});
const ChatMessagesView(this.groupId, {super.key});
final Group group;
final String groupId;
@override
State<ChatMessagesView> createState() => _ChatMessagesViewState();
@ -64,12 +35,13 @@ class ChatMessagesView extends StatefulWidget {
class _ChatMessagesViewState extends State<ChatMessagesView> {
HashSet<int> alreadyReportedOpened = HashSet<int>();
late Group group;
late StreamSubscription<Group?> userSub;
late StreamSubscription<List<Message>> messageSub;
StreamSubscription<List<GroupHistory>>? groupActionsSub;
StreamSubscription<List<Contact>>? contactSub;
Group? _group;
Map<int, Contact> userIdToContact = {};
List<ChatItem> messages = [];
@ -85,7 +57,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
@override
void initState() {
super.initState();
group = widget.group;
textFieldFocus = FocusNode();
initStreams();
}
@ -102,16 +73,18 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
Mutex protectMessageUpdating = Mutex();
Future<void> initStreams() async {
final groupStream = twonlyDB.groupsDao.watchGroup(group.groupId);
final groupStream = twonlyDB.groupsDao.watchGroup(widget.groupId);
userSub = groupStream.listen((newGroup) {
if (newGroup == null) return;
setState(() {
group = newGroup;
});
_group = newGroup;
});
if (!widget.group.isDirectChat) {
final actionsStream = twonlyDB.groupsDao.watchGroupActions(group.groupId);
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);
@ -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 {
allMessages = update;
await protectMessageUpdating.protect(() async {
@ -153,8 +128,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
if (groupHistoryIndex < groupActions.length) {
for (; groupHistoryIndex < groupActions.length; groupHistoryIndex++) {
if (msg.createdAt.isAfter(groupActions[groupHistoryIndex].actionAt)) {
chatItems
.add(ChatItem.groupAction(groupActions[groupHistoryIndex]));
chatItems.add(
ChatItem.groupAction(groupActions[groupHistoryIndex]),
);
// groupHistoryIndex++;
} else {
break;
@ -230,6 +206,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
@override
Widget build(BuildContext context) {
if (_group == null) return Container();
final group = _group!;
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
@ -237,12 +215,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
title: GestureDetector(
onTap: () async {
if (group.isDirectChat) {
final member =
await twonlyDB.groupsDao.getAllGroupMembers(group.groupId);
final member = await twonlyDB.groupsDao.getAllGroupMembers(
group.groupId,
);
if (!context.mounted) return;
if (member.isEmpty) return;
await context
.push(Routes.profileContact(member.first.contactId));
await context.push(
Routes.profileContact(member.first.contactId),
);
} else {
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 {
final messages =
twonlyDB.messagesDao.watchMediaNotOpened(widget.group.groupId);
final messages = twonlyDB.messagesDao.watchMediaNotOpened(
widget.group.groupId,
);
_subscription = messages.listen((messages) async {
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...
final index =
allMediaFiles.indexWhere((m) => m.messageId == msg.messageId);
final index = allMediaFiles.indexWhere(
(m) => m.messageId == msg.messageId,
);
if (index >= 1) {
allMediaFiles[index] = msg;
@ -160,7 +162,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
if (group != null &&
group.draftMessage != null &&
group.draftMessage != '') {
context.replace(Routes.chatsMessages, extra: group);
context.replace(Routes.chatsMessages(group.groupId));
} else {
Navigator.pop(context);
}
@ -190,8 +192,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
unawaited(flutterLocalNotificationsPlugin.cancelAll());
final stream =
twonlyDB.mediaFilesDao.watchMedia(allMediaFiles.first.mediaId!);
final stream = twonlyDB.mediaFilesDao.watchMedia(
allMediaFiles.first.mediaId!,
);
var downloadTriggered = false;
@ -204,8 +207,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
});
if (!downloadTriggered) {
downloadTriggered = true;
final mediaFile = await twonlyDB.mediaFilesDao
.getMediaFileById(allMediaFiles.first.mediaId!);
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
allMediaFiles.first.mediaId!,
);
if (mediaFile == null) return;
await startDownloadMedia(mediaFile, true);
unawaited(tryDownloadAllMediaFiles(force: true));
@ -226,8 +230,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
setState(() {
_showDownloadingLoader = false;
});
final currentMediaLocal =
await MediaFileService.fromMediaId(allMediaFiles.first.mediaId!);
final currentMediaLocal = await MediaFileService.fromMediaId(
allMediaFiles.first.mediaId!,
);
if (currentMediaLocal == null || !mounted) return;
if (currentMediaLocal.mediaFile.requiresAuthentication) {
@ -259,8 +264,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
});
if (!widget.group.isDirectChat) {
final sender =
await twonlyDB.contactsDao.getContactById(currentMessage!.senderId!);
final sender = await twonlyDB.contactsDao.getContactById(
currentMessage!.senderId!,
);
if (sender != null) {
_currentMediaSender =
'${getContactDisplayName(sender)} (${widget.group.groupName})';
@ -285,24 +291,29 @@ class _MediaViewerViewState extends State<MediaViewerView> {
await videoController?.setLooping(
currentMediaLocal.mediaFile.displayLimitInMilliseconds == null,
);
await videoController?.initialize().then((_) {
await videoController
?.initialize()
.then((_) {
if (videoController == null) return;
videoController?.play();
videoController?.addListener(() {
setState(() {
progress = 1 -
progress =
1 -
videoController!.value.position.inSeconds /
videoController!.value.duration.inSeconds;
});
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) {
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds !=
null) {
if (videoController?.value.position ==
videoController?.value.duration) {
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 {
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds != null) {
canBeSeenUntil = clock.now().add(
@ -434,8 +445,8 @@ class _MediaViewerViewState extends State<MediaViewerView> {
height: 8,
child: Center(
child: EmojiAnimation(
emoji:
EmojiAnimation.animatedIcons.keys.toList()[index],
emoji: EmojiAnimation.animatedIcons.keys
.toList()[index],
),
),
);

View file

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

View file

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

View file

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

View file

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

View file

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