mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-04-18 14:22:53 +00:00
add troubleshooting for notifications & video stab
This commit is contained in:
parent
c48ce31bc6
commit
955992d5d2
25 changed files with 763 additions and 479 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ class UserData {
|
|||
@JsonKey(defaultValue: false)
|
||||
bool requestedAudioPermission = false;
|
||||
|
||||
@JsonKey(defaultValue: false)
|
||||
bool videoStabilizationEnabled = false;
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
bool showFeedbackShortcut = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,46 +42,69 @@ 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 {
|
||||
await updateUserdata((u) {
|
||||
u.updateFCMToken = true;
|
||||
return u;
|
||||
});
|
||||
await storage.write(key: SecureStorageKeys.googleFcm, value: fcmToken);
|
||||
}).onError((err) {
|
||||
Log.error('could not listen on token refresh');
|
||||
});
|
||||
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;
|
||||
});
|
||||
})
|
||||
.onError((err) {
|
||||
Log.error('could not listen on token refresh');
|
||||
});
|
||||
} catch (e) {
|
||||
Log.error('could not load fcm token: $e');
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
await Firebase.initializeApp(
|
||||
options: DefaultFirebaseOptions.currentPlatform,
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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,25 +343,28 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
await deInitVolumeControl();
|
||||
if (!mounted) return true;
|
||||
|
||||
final shouldReturn = await Navigator.push(
|
||||
context,
|
||||
PageRouteBuilder(
|
||||
opaque: false,
|
||||
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
||||
screenshotImage: screenshotImage,
|
||||
sharedFromGallery: sharedFromGallery,
|
||||
sendToGroup: widget.sendToGroup,
|
||||
mediaFileService: mediaFileService,
|
||||
mainCameraController: mc,
|
||||
previewLink: mc.sharedLinkForPreview,
|
||||
),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return child;
|
||||
},
|
||||
transitionDuration: Duration.zero,
|
||||
reverseTransitionDuration: Duration.zero,
|
||||
),
|
||||
) as bool?;
|
||||
final shouldReturn =
|
||||
await Navigator.push(
|
||||
context,
|
||||
PageRouteBuilder(
|
||||
opaque: false,
|
||||
pageBuilder: (context, a1, a2) => ShareImageEditorView(
|
||||
screenshotImage: screenshotImage,
|
||||
sharedFromGallery: sharedFromGallery,
|
||||
sendToGroup: widget.sendToGroup,
|
||||
mediaFileService: mediaFileService,
|
||||
mainCameraController: mc,
|
||||
previewLink: mc.sharedLinkForPreview,
|
||||
),
|
||||
transitionsBuilder:
|
||||
(context, animation, secondaryAnimation, child) {
|
||||
return child;
|
||||
},
|
||||
transitionDuration: Duration.zero,
|
||||
reverseTransitionDuration: Duration.zero,
|
||||
),
|
||||
)
|
||||
as bool?;
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
mc.isSharePreviewIsShown = false;
|
||||
|
|
@ -396,13 +402,15 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
return;
|
||||
}
|
||||
|
||||
mc.selectedCameraDetails.scaleFactor = (_baseScaleFactor +
|
||||
// ignore: avoid_dynamic_calls
|
||||
(_basePanY - (details.localPosition.dy as double)) / 30)
|
||||
.clamp(1, mc.selectedCameraDetails.maxAvailableZoom);
|
||||
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(() {});
|
||||
|
|
@ -739,8 +762,8 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
child: FaIcon(
|
||||
mc.isSelectingFaceFilters
|
||||
? mc.currentFilterType.index == 1
|
||||
? FontAwesomeIcons.xmark
|
||||
: FontAwesomeIcons.arrowLeft
|
||||
? FontAwesomeIcons.xmark
|
||||
: FontAwesomeIcons.arrowLeft
|
||||
: FontAwesomeIcons.photoFilm,
|
||||
color: Colors.white,
|
||||
size: 25,
|
||||
|
|
@ -785,13 +808,14 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
child: FaIcon(
|
||||
mc.isSelectingFaceFilters
|
||||
? mc.currentFilterType.index ==
|
||||
FaceFilterType
|
||||
.values.length -
|
||||
1
|
||||
? FontAwesomeIcons.xmark
|
||||
: FontAwesomeIcons.arrowRight
|
||||
FaceFilterType
|
||||
.values
|
||||
.length -
|
||||
1
|
||||
? FontAwesomeIcons.xmark
|
||||
: FontAwesomeIcons.arrowRight
|
||||
: FontAwesomeIcons
|
||||
.faceGrinTongueSquint,
|
||||
.faceGrinTongueSquint,
|
||||
color: Colors.white,
|
||||
size: 25,
|
||||
),
|
||||
|
|
@ -843,64 +867,64 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
children: [
|
||||
...widget.mainCameraController.scannedNewProfiles.values
|
||||
.map(
|
||||
(c) {
|
||||
if (c.isLoading) return Container();
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
c.isLoading = true;
|
||||
widget.mainCameraController.setState();
|
||||
if (await addNewContactFromPublicProfile(
|
||||
c.profile,
|
||||
) &&
|
||||
context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.lang.requestedUserToastText(
|
||||
c.profile.username,
|
||||
(c) {
|
||||
if (c.isLoading) return Container();
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
c.isLoading = true;
|
||||
widget.mainCameraController.setState();
|
||||
if (await addNewContactFromPublicProfile(
|
||||
c.profile,
|
||||
) &&
|
||||
context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
context.lang.requestedUserToastText(
|
||||
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(
|
||||
(c) {
|
||||
return Container(
|
||||
|
|
@ -936,10 +960,13 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
|||
: 'assets/animations/failed.lottie',
|
||||
repeat: false,
|
||||
onLoaded: (p0) {
|
||||
Future.delayed(const Duration(seconds: 4),
|
||||
() {
|
||||
widget.mainCameraController.setState();
|
||||
});
|
||||
Future.delayed(
|
||||
const Duration(seconds: 4),
|
||||
() {
|
||||
widget.mainCameraController
|
||||
.setState();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -64,41 +64,43 @@ class _UserListItem extends State<GroupListItem> {
|
|||
_lastMessageStream = twonlyDB.messagesDao
|
||||
.watchLastMessage(widget.group.groupId)
|
||||
.listen((update) {
|
||||
protectUpdateState.protect(() async {
|
||||
await updateState(update, _messagesNotOpened);
|
||||
});
|
||||
});
|
||||
protectUpdateState.protect(() async {
|
||||
await updateState(update, _messagesNotOpened);
|
||||
});
|
||||
});
|
||||
|
||||
_lastReactionStream = twonlyDB.reactionsDao
|
||||
.watchLastReactions(widget.group.groupId)
|
||||
.listen((update) {
|
||||
setState(() {
|
||||
_lastReaction = update;
|
||||
});
|
||||
// protectUpdateState.protect(() async {
|
||||
// await updateState(lastMessage, update, messagesNotOpened);
|
||||
// });
|
||||
});
|
||||
setState(() {
|
||||
_lastReaction = update;
|
||||
});
|
||||
// protectUpdateState.protect(() async {
|
||||
// await updateState(lastMessage, update, messagesNotOpened);
|
||||
// });
|
||||
});
|
||||
|
||||
_messagesNotOpenedStream = twonlyDB.messagesDao
|
||||
.watchMessageNotOpened(widget.group.groupId)
|
||||
.listen((update) {
|
||||
protectUpdateState.protect(() async {
|
||||
await updateState(_lastMessage, update);
|
||||
});
|
||||
});
|
||||
protectUpdateState.protect(() async {
|
||||
await updateState(_lastMessage, update);
|
||||
});
|
||||
});
|
||||
|
||||
_lastMediaFilesStream =
|
||||
twonlyDB.mediaFilesDao.watchNewestMediaFiles().listen((mediaFiles) {
|
||||
for (final mediaFile in mediaFiles) {
|
||||
final index = _previewMediaFiles
|
||||
.indexWhere((t) => t.mediaId == mediaFile.mediaId);
|
||||
if (index >= 0) {
|
||||
_previewMediaFiles[index] = mediaFile;
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
_lastMediaFilesStream = twonlyDB.mediaFilesDao
|
||||
.watchNewestMediaFiles()
|
||||
.listen((mediaFiles) {
|
||||
for (final mediaFile in mediaFiles) {
|
||||
final index = _previewMediaFiles.indexWhere(
|
||||
(t) => t.mediaId == mediaFile.mediaId,
|
||||
);
|
||||
if (index >= 0) {
|
||||
_previewMediaFiles[index] = mediaFile;
|
||||
}
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
Mutex protectUpdateState = Mutex();
|
||||
|
|
@ -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
|
||||
|
|
@ -206,18 +208,18 @@ class _UserListItem extends State<GroupListItem> {
|
|||
),
|
||||
subtitle: (_currentMessage == null)
|
||||
? (widget.group.totalMediaCounter == 0)
|
||||
? Text(context.lang.chatsTapToSend)
|
||||
: Row(
|
||||
children: [
|
||||
LastMessageTime(
|
||||
dateTime: widget.group.lastMessageExchange,
|
||||
),
|
||||
FlameCounterWidget(
|
||||
groupId: widget.group.groupId,
|
||||
prefix: true,
|
||||
),
|
||||
],
|
||||
)
|
||||
? Text(context.lang.chatsTapToSend)
|
||||
: Row(
|
||||
children: [
|
||||
LastMessageTime(
|
||||
dateTime: widget.group.lastMessageExchange,
|
||||
),
|
||||
FlameCounterWidget(
|
||||
groupId: widget.group.groupId,
|
||||
prefix: true,
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
MessageSendStateIcon(
|
||||
|
|
@ -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,
|
||||
extra: widget.group,
|
||||
),
|
||||
onPressed: () {
|
||||
if (_hasNonOpenedMediaFile) {
|
||||
context.push(Routes.chatsMessages(widget.group.groupId));
|
||||
} else {
|
||||
context.push(
|
||||
Routes.chatsCameraSendTo,
|
||||
extra: widget.group,
|
||||
);
|
||||
}
|
||||
},
|
||||
icon: FaIcon(
|
||||
_hasNonOpenedMediaFile
|
||||
? FontAwesomeIcons.solidComments
|
||||
|
|
|
|||
|
|
@ -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,30 +73,34 @@ 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;
|
||||
});
|
||||
|
||||
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 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);
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,32 +291,37 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
await videoController?.setLooping(
|
||||
currentMediaLocal.mediaFile.displayLimitInMilliseconds == null,
|
||||
);
|
||||
await videoController?.initialize().then((_) {
|
||||
if (videoController == null) return;
|
||||
videoController?.play();
|
||||
videoController?.addListener(() {
|
||||
setState(() {
|
||||
progress = 1 -
|
||||
videoController!.value.position.inSeconds /
|
||||
videoController!.value.duration.inSeconds;
|
||||
});
|
||||
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);
|
||||
await videoController
|
||||
?.initialize()
|
||||
.then((_) {
|
||||
if (videoController == null) return;
|
||||
videoController?.play();
|
||||
videoController?.addListener(() {
|
||||
setState(() {
|
||||
progress =
|
||||
1 -
|
||||
videoController!.value.position.inSeconds /
|
||||
videoController!.value.duration.inSeconds;
|
||||
});
|
||||
if (currentMediaLocal.mediaFile.displayLimitInMilliseconds !=
|
||||
null) {
|
||||
if (videoController?.value.position ==
|
||||
videoController?.value.duration) {
|
||||
nextMediaOrExit();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
// 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(
|
||||
Duration(
|
||||
milliseconds:
|
||||
currentMediaLocal.mediaFile.displayLimitInMilliseconds!,
|
||||
),
|
||||
);
|
||||
Duration(
|
||||
milliseconds:
|
||||
currentMediaLocal.mediaFile.displayLimitInMilliseconds!,
|
||||
),
|
||||
);
|
||||
timerRequired = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -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],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,13 +47,14 @@ class _StartNewChatView extends State<StartNewChatView> {
|
|||
await filterUsers();
|
||||
});
|
||||
|
||||
allNonDirectGroupsSub =
|
||||
twonlyDB.groupsDao.watchGroupsForStartNewChat().listen((update) async {
|
||||
setState(() {
|
||||
allNonDirectGroups = update;
|
||||
});
|
||||
await filterUsers();
|
||||
});
|
||||
allNonDirectGroupsSub = twonlyDB.groupsDao
|
||||
.watchGroupsForStartNewChat()
|
||||
.listen((update) async {
|
||||
setState(() {
|
||||
allNonDirectGroups = update;
|
||||
});
|
||||
await filterUsers();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -45,8 +46,8 @@ class _ContactViewState extends State<ContactView> {
|
|||
_groupMemberSub = twonlyDB.groupsDao
|
||||
.watchContactGroupMember(widget.userId)
|
||||
.listen((groups) async {
|
||||
_memberOfGroups = groups;
|
||||
});
|
||||
_memberOfGroups = groups;
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
final group = await twonlyDB.groupsDao.getDirectChat(
|
||||
contact.userId,
|
||||
);
|
||||
if (group != null && context.mounted) {
|
||||
await context.push(
|
||||
Routes.chatsMessages,
|
||||
extra: group,
|
||||
);
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -13,9 +13,87 @@ 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
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -27,47 +105,32 @@ class NotificationView extends StatelessWidget {
|
|||
ListTile(
|
||||
title: Text(context.lang.settingsNotifyTroubleshooting),
|
||||
subtitle: Text(context.lang.settingsNotifyTroubleshootingDesc),
|
||||
onTap: () async {
|
||||
await initFCMAfterAuthenticated();
|
||||
final storedToken = await (const FlutterSecureStorage()
|
||||
.read(key: SecureStorageKeys.googleFcm));
|
||||
await setupNotificationWithUsers(force: true);
|
||||
if (!context.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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
214
pubspec.lock
214
pubspec.lock
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue