diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 9d7850c..a0ff4e0 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -489,7 +489,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 77;
+ CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -511,7 +511,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 77;
+ CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -530,7 +530,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 77;
+ CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -547,7 +547,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 77;
+ CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = JND55328G8;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
@@ -679,7 +679,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 77;
+ CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -702,7 +702,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 77;
+ CURRENT_PROJECT_VERSION = 80;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 8acb499..4ec43d7 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -43,7 +43,7 @@
CFBundleSignature
????
CFBundleVersion
- 77
+ 80
LSRequiresIPhoneOS
NSBluetoothAlwaysUsageDescription
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
index db2228f..a253cf9 100644
--- a/ios/fastlane/report.xml
+++ b/ios/fastlane/report.xml
@@ -5,22 +5,17 @@
-
+
-
+
-
-
-
-
-
-
+
diff --git a/lib/widgets/messages/image_message_bubble.dart b/lib/widgets/messages/image_message_bubble.dart
index ae11fb0..db028f0 100644
--- a/lib/widgets/messages/image_message_bubble.dart
+++ b/lib/widgets/messages/image_message_bubble.dart
@@ -212,9 +212,11 @@ class _ImageMessageBubbleState extends State {
int pathLen = 0,
}) async {
if (_isRequesting) return;
+ final conn = context.read();
var sender = _resolveSender(envelope);
- if (sender == null) {
- final conn = context.read();
+ if (sender == null ||
+ sender.outPathLen < 0 ||
+ sender.outPathLen > _maxFetchHops) {
await conn.getContacts();
if (!mounted) return;
sender = _resolveSender(envelope);
@@ -247,7 +249,6 @@ class _ImageMessageBubbleState extends State {
}
setState(() => _errorText = null);
- final conn = context.read();
final imageProvider = context.read();
final deviceKey = conn.deviceInfo.publicKey;
if (deviceKey == null || deviceKey.length < 6) {
@@ -295,6 +296,7 @@ class _ImageMessageBubbleState extends State {
);
} catch (_) {
if (mounted) {
+ _showToast('Image fetch failed to send request');
setState(() {
_isRequesting = false;
_errorText = 'Image unavailable right now';
@@ -323,7 +325,11 @@ class _ImageMessageBubbleState extends State {
if (mounted &&
_isRequesting &&
!imageProvider.isComplete(envelope.sessionId)) {
- setState(() => _isRequesting = false);
+ _showToast('Image fetch timed out');
+ setState(() {
+ _isRequesting = false;
+ _errorText = 'Image fetch timed out';
+ });
}
},
);
@@ -331,19 +337,8 @@ class _ImageMessageBubbleState extends State {
Contact? _resolveSender(ImageEnvelope envelope) {
final contactsProvider = context.read();
- final senderPrefix = widget.message.senderPublicKeyPrefix;
- if (senderPrefix != null && senderPrefix.length >= 6) {
- final c = contactsProvider.findContactByPrefix(
- Uint8List.fromList(senderPrefix.sublist(0, 6)),
- );
- if (c != null) return c;
- }
- final contact = contactsProvider.findContactByPrefixHex(
- envelope.senderKey6,
- );
- if (contact != null) return contact;
- // For sent direct messages, fetch must target the recipient peer.
+ // For sent direct messages, fetch must target the recipient peer first.
final recipientKey = widget.message.recipientPublicKey;
if (widget.isSentByMe && recipientKey != null && recipientKey.isNotEmpty) {
final byKey = contactsProvider.findContactByKey(recipientKey);
@@ -356,6 +351,18 @@ class _ImageMessageBubbleState extends State {
}
}
+ final senderPrefix = widget.message.senderPublicKeyPrefix;
+ if (senderPrefix != null && senderPrefix.length >= 6) {
+ final c = contactsProvider.findContactByPrefix(
+ Uint8List.fromList(senderPrefix.sublist(0, 6)),
+ );
+ if (c != null) return c;
+ }
+ final contact = contactsProvider.findContactByPrefixHex(
+ envelope.senderKey6,
+ );
+ if (contact != null) return contact;
+
final senderName = widget.message.senderName?.trim();
if (senderName != null && senderName.isNotEmpty) {
for (final c in contactsProvider.contacts) {
@@ -377,6 +384,7 @@ class _ImageMessageBubbleState extends State {
Future _showBlockingAlert(String title, String message) async {
if (!mounted) return;
+ _showToast('$title: $message');
await showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
diff --git a/lib/widgets/messages/voice_message_bubble.dart b/lib/widgets/messages/voice_message_bubble.dart
index 07a2cc3..28260d7 100644
--- a/lib/widgets/messages/voice_message_bubble.dart
+++ b/lib/widgets/messages/voice_message_bubble.dart
@@ -207,9 +207,11 @@ class _VoiceMessageBubbleState extends State {
int pathLen = 0,
}) async {
if (_isRequesting) return;
+ final connectionProvider = context.read();
var sender = _resolveSenderContact();
- if (sender == null) {
- final connectionProvider = context.read();
+ if (sender == null ||
+ sender.outPathLen < 0 ||
+ sender.outPathLen > _maxFetchHops) {
await connectionProvider.getContacts();
if (!mounted) return;
sender = _resolveSenderContact();
@@ -246,7 +248,6 @@ class _VoiceMessageBubbleState extends State {
_errorText = null;
});
- final connectionProvider = context.read();
final deviceKey = connectionProvider.deviceInfo.publicKey;
if (deviceKey == null || deviceKey.length < 6) {
await _showBlockingAlert(
@@ -310,6 +311,7 @@ class _VoiceMessageBubbleState extends State {
void _setUnavailable() {
if (!mounted) return;
+ _showToast(AppLocalizations.of(context)!.voiceUnavailable);
setState(() {
_isRequesting = false;
_autoPlayWhenReady = false;
@@ -319,6 +321,20 @@ class _VoiceMessageBubbleState extends State {
Contact? _resolveSenderContact() {
final contactsProvider = context.read();
+
+ // For sent direct messages, fetch must target the recipient peer first.
+ final recipientKey = widget.message.recipientPublicKey;
+ if (widget.isSentByMe && recipientKey != null && recipientKey.isNotEmpty) {
+ final byKey = contactsProvider.findContactByKey(recipientKey);
+ if (byKey != null) return byKey;
+ if (recipientKey.length >= 6) {
+ final byPrefix = contactsProvider.findContactByPrefix(
+ Uint8List.fromList(recipientKey.sublist(0, 6)),
+ );
+ if (byPrefix != null) return byPrefix;
+ }
+ }
+
final senderPrefix = widget.message.senderPublicKeyPrefix;
if (senderPrefix != null && senderPrefix.length >= 6) {
final contact = contactsProvider.findContactByPrefix(
@@ -335,19 +351,6 @@ class _VoiceMessageBubbleState extends State {
if (contact != null) return contact;
}
- // For sent direct messages, fetch must target the recipient peer.
- final recipientKey = widget.message.recipientPublicKey;
- if (widget.isSentByMe && recipientKey != null && recipientKey.isNotEmpty) {
- final byKey = contactsProvider.findContactByKey(recipientKey);
- if (byKey != null) return byKey;
- if (recipientKey.length >= 6) {
- final byPrefix = contactsProvider.findContactByPrefix(
- Uint8List.fromList(recipientKey.sublist(0, 6)),
- );
- if (byPrefix != null) return byPrefix;
- }
- }
-
final senderName = widget.message.senderName?.trim();
if (senderName != null && senderName.isNotEmpty) {
for (final contact in contactsProvider.contacts) {
@@ -369,6 +372,7 @@ class _VoiceMessageBubbleState extends State {
Future _showBlockingAlert(String title, String message) async {
if (!mounted) return;
+ _showToast('$title: $message');
await showDialog(
context: context,
builder: (dialogContext) => AlertDialog(