diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 9e008fc..b73764d 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 = 92;
+ CURRENT_PROJECT_VERSION = 94;
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 = 92;
+ CURRENT_PROJECT_VERSION = 94;
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 = 92;
+ CURRENT_PROJECT_VERSION = 94;
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 = 92;
+ CURRENT_PROJECT_VERSION = 94;
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 = 92;
+ CURRENT_PROJECT_VERSION = 94;
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 = 92;
+ CURRENT_PROJECT_VERSION = 94;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index a503442..f0915a9 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -43,7 +43,7 @@
CFBundleSignature
????
CFBundleVersion
- 92
+ 94
LSRequiresIPhoneOS
NSBluetoothAlwaysUsageDescription
diff --git a/ios/fastlane/report.xml b/ios/fastlane/report.xml
index e4bef8c..330f28d 100644
--- a/ios/fastlane/report.xml
+++ b/ios/fastlane/report.xml
@@ -5,22 +5,22 @@
-
+
-
+
-
+
-
+
diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart
index c3b9101..e3b4056 100644
--- a/lib/screens/messages_tab.dart
+++ b/lib/screens/messages_tab.dart
@@ -466,7 +466,10 @@ class _MessagesTabState extends State {
);
// Add to messages list with "sending" status
- messagesProvider.addSentMessage(sentMessage);
+ messagesProvider.addSentMessage(
+ sentMessage,
+ contact: _selectedRecipient,
+ );
// Send message to selected recipient
final sentSuccessfully = await connectionProvider.sendTextMessage(
@@ -1498,145 +1501,163 @@ class _MessagesTabState extends State {
return Consumer(
builder: (context, messagesProvider, child) {
final messages = _getFilteredMessages(messagesProvider);
+ final bottomInset = MediaQuery.of(context).viewPadding.bottom;
+ final composerBottomPadding = bottomInset > 0 ? 2.0 : 10.0;
- return Column(
- children: [
- // Messages list with pull-to-refresh
- Expanded(
- child: RefreshIndicator(
- onRefresh: _handleRefresh,
- child: messages.isEmpty
- ? LayoutBuilder(
- builder: (context, constraints) =>
- SingleChildScrollView(
- physics: const AlwaysScrollableScrollPhysics(),
- child: ConstrainedBox(
- constraints: BoxConstraints(
- minHeight: constraints.maxHeight,
- ),
- child: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(
- Icons.message_outlined,
- size: 64,
- color: Theme.of(context).disabledColor,
- ),
- const SizedBox(height: 16),
- Text(
- AppLocalizations.of(
- context,
- )!.noMessagesYet,
- style: Theme.of(
- context,
- ).textTheme.titleLarge,
- ),
- const SizedBox(height: 8),
- Text(
- AppLocalizations.of(
- context,
- )!.pullDownToSync,
- style: Theme.of(
- context,
- ).textTheme.bodyMedium,
- textAlign: TextAlign.center,
- ),
- ],
+ return GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ onTap: () => FocusScope.of(context).unfocus(),
+ child: Column(
+ children: [
+ // Messages list with pull-to-refresh
+ Expanded(
+ child: RefreshIndicator(
+ onRefresh: _handleRefresh,
+ child: messages.isEmpty
+ ? LayoutBuilder(
+ builder: (context, constraints) =>
+ SingleChildScrollView(
+ keyboardDismissBehavior:
+ ScrollViewKeyboardDismissBehavior.onDrag,
+ physics:
+ const AlwaysScrollableScrollPhysics(),
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ minHeight: constraints.maxHeight,
+ ),
+ child: Center(
+ child: Column(
+ mainAxisAlignment:
+ MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.message_outlined,
+ size: 64,
+ color: Theme.of(
+ context,
+ ).disabledColor,
+ ),
+ const SizedBox(height: 16),
+ Text(
+ AppLocalizations.of(
+ context,
+ )!.noMessagesYet,
+ style: Theme.of(
+ context,
+ ).textTheme.titleLarge,
+ ),
+ const SizedBox(height: 8),
+ Text(
+ AppLocalizations.of(
+ context,
+ )!.pullDownToSync,
+ style: Theme.of(
+ context,
+ ).textTheme.bodyMedium,
+ textAlign: TextAlign.center,
+ ),
+ ],
+ ),
),
),
),
- ),
- )
- : ListView.builder(
- controller: _scrollController,
- reverse: true,
- padding: const EdgeInsets.all(8),
- itemCount: messages.length,
- itemBuilder: (context, index) {
- final message = messages[index];
- final isHighlighted =
- message.id == _highlightedMessageId;
+ )
+ : ListView.builder(
+ controller: _scrollController,
+ keyboardDismissBehavior:
+ ScrollViewKeyboardDismissBehavior.onDrag,
+ reverse: true,
+ padding: const EdgeInsets.all(8),
+ itemCount: messages.length,
+ itemBuilder: (context, index) {
+ final message = messages[index];
+ final isHighlighted =
+ message.id == _highlightedMessageId;
- return MessageBubble(
- key: ValueKey(message.id),
- message: message,
- isHighlighted: isHighlighted,
- onNavigateToMap: widget.onNavigateToMap,
- onTap:
- widget.onNavigateToMap != null &&
- message.isSarMarker &&
- message.sarGpsCoordinates != null
- ? () {
- final mapProvider = context
- .read();
- mapProvider.navigateToLocation(
- location: message.sarGpsCoordinates!,
- zoom: 15.0,
- );
- widget.onNavigateToMap?.call();
- }
- : widget.onNavigateToMap != null &&
- message.isDrawing &&
- message.drawingId != null
- ? () {
- debugPrint(
- 'πΊοΈ [MessagesTab] Drawing tapped! ID: ${message.drawingId}',
- );
- final mapProvider = context
- .read();
- final drawingProvider = context
- .read();
- mapProvider.navigateToDrawing(
- message.drawingId!,
- drawingProvider,
- );
- widget.onNavigateToMap?.call();
- }
- : null,
- );
- },
- ),
+ return MessageBubble(
+ key: ValueKey(message.id),
+ message: message,
+ isHighlighted: isHighlighted,
+ onNavigateToMap: widget.onNavigateToMap,
+ onTap:
+ widget.onNavigateToMap != null &&
+ message.isSarMarker &&
+ message.sarGpsCoordinates != null
+ ? () {
+ final mapProvider = context
+ .read();
+ mapProvider.navigateToLocation(
+ location: message.sarGpsCoordinates!,
+ zoom: 15.0,
+ );
+ widget.onNavigateToMap?.call();
+ }
+ : widget.onNavigateToMap != null &&
+ message.isDrawing &&
+ message.drawingId != null
+ ? () {
+ debugPrint(
+ 'πΊοΈ [MessagesTab] Drawing tapped! ID: ${message.drawingId}',
+ );
+ final mapProvider = context
+ .read();
+ final drawingProvider = context
+ .read();
+ mapProvider.navigateToDrawing(
+ message.drawingId!,
+ drawingProvider,
+ );
+ widget.onNavigateToMap?.call();
+ }
+ : null,
+ );
+ },
+ ),
+ ),
),
- ),
- // Message input area
- Container(
- decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.surface,
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- SafeArea(
- top: false,
- child: Padding(
- padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
- child: Container(
- decoration: BoxDecoration(
- color: Theme.of(
- context,
- ).colorScheme.surfaceContainerLow,
- borderRadius: BorderRadius.circular(28),
- border: Border.all(
+ // Message input area
+ Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SafeArea(
+ top: false,
+ child: Padding(
+ padding: EdgeInsets.fromLTRB(
+ 10,
+ 10,
+ 10,
+ composerBottomPadding,
+ ),
+ child: Container(
+ decoration: BoxDecoration(
color: Theme.of(
context,
- ).dividerColor.withValues(alpha: 0.35),
- ),
- boxShadow: [
- BoxShadow(
- color: Colors.black.withValues(alpha: 0.05),
- blurRadius: 18,
- offset: const Offset(0, 6),
+ ).colorScheme.surfaceContainerLow,
+ borderRadius: BorderRadius.circular(28),
+ border: Border.all(
+ color: Theme.of(
+ context,
+ ).dividerColor.withValues(alpha: 0.35),
),
- ],
- ),
- child: Padding(
- padding: const EdgeInsets.fromLTRB(10, 10, 10, 8),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withValues(alpha: 0.05),
+ blurRadius: 18,
+ offset: const Offset(0, 6),
+ ),
+ ],
+ ),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(10, 10, 10, 8),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
Row(
children: [
Container(
@@ -1860,20 +1881,52 @@ class _MessagesTabState extends State {
excludeFromSemantics: true,
child: GestureDetector(
excludeFromSemantics: true,
+ onTap: canSendText
+ ? () {
+ debugPrint(
+ 'π [MessagesTab] Send button tapped '
+ '(canSendText=$canSendText, '
+ 'textLength=${_textController.text.trim().length}, '
+ 'recording=$_isRecording, '
+ 'sendingVoice=$_isSendingVoice)',
+ );
+ _sendMessage();
+ }
+ : null,
onLongPressStart:
(_voiceSupported &&
!_isSendingVoice)
- ? (_) => _startVoiceRecording()
+ ? (_) {
+ debugPrint(
+ 'ποΈ [MessagesTab] Send button long-press start '
+ '(voiceSupported=$_voiceSupported, '
+ 'sendingVoice=$_isSendingVoice, '
+ 'recording=$_isRecording)',
+ );
+ _startVoiceRecording();
+ }
: null,
onLongPressEnd:
(_voiceSupported &&
_isRecording)
- ? (_) => _stopAndSendVoice()
+ ? (_) {
+ debugPrint(
+ 'ποΈ [MessagesTab] Send button long-press end '
+ '(recording=$_isRecording)',
+ );
+ _stopAndSendVoice();
+ }
: null,
onLongPressCancel:
(_voiceSupported &&
_isRecording)
- ? () => _stopAndSendVoice()
+ ? () {
+ debugPrint(
+ 'ποΈ [MessagesTab] Send button long-press cancel '
+ '(recording=$_isRecording)',
+ );
+ _stopAndSendVoice();
+ }
: null,
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -1993,16 +2046,17 @@ class _MessagesTabState extends State {
),
],
),
- ],
+ ],
+ ),
),
),
),
),
- ),
- ],
+ ],
+ ),
),
- ),
- ],
+ ],
+ ),
);
},
);
diff --git a/pubspec.yaml b/pubspec.yaml
index f7d342e..a5df551 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
-version: 2026.0306.2+12
+version: 2026.0306.3+13
environment:
sdk: ^3.9.2