This commit is contained in:
Janez T
2025-10-15 19:57:10 +02:00
parent 24234e7000
commit f5a6e1f9ab
13 changed files with 297 additions and 268 deletions

View File

@@ -1,38 +1,4 @@
PODS: PODS:
- DKImagePickerController/Core (4.3.9):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.9)
- DKImagePickerController/PhotoGallery (4.3.9):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.9)
- DKPhotoGallery (0.0.19):
- DKPhotoGallery/Core (= 0.0.19)
- DKPhotoGallery/Model (= 0.0.19)
- DKPhotoGallery/Preview (= 0.0.19)
- DKPhotoGallery/Resource (= 0.0.19)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.19):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.19):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_background_service_ios (0.0.3): - flutter_background_service_ios (0.0.3):
- Flutter - Flutter
@@ -55,18 +21,13 @@ PODS:
- FlutterMacOS - FlutterMacOS
- permission_handler_apple (9.3.0): - permission_handler_apple (9.3.0):
- Flutter - Flutter
- SDWebImage (5.21.3):
- SDWebImage/Core (= 5.21.3)
- SDWebImage/Core (5.21.3)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- SwiftyGif (5.4.5)
DEPENDENCIES: DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`) - flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`)
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`) - flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
@@ -81,15 +42,9 @@ DEPENDENCIES:
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- DKImagePickerController
- DKPhotoGallery
- ObjectBox - ObjectBox
- SDWebImage
- SwiftyGif
EXTERNAL SOURCES: EXTERNAL SOURCES:
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_background_service_ios: flutter_background_service_ios:
@@ -114,9 +69,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
SPEC CHECKSUMS: SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_background_service_ios: 00d31bdff7b4bfe06d32375df358abe0329cf87e flutter_background_service_ios: 00d31bdff7b4bfe06d32375df358abe0329cf87e
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3 flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
@@ -127,10 +79,8 @@ SPEC CHECKSUMS:
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e

BIN
ios/Runner.app.dSYM.zip Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -489,7 +489,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JND55328G8; DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -511,7 +511,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 4;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
@@ -529,7 +529,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 4;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
@@ -545,7 +545,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 4;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
@@ -676,7 +676,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JND55328G8; DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -699,7 +699,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 2; CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = JND55328G8; DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -21,7 +21,7 @@
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>2</string> <string>4</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>

View File

@@ -5,7 +5,24 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000355"> <testcase classname="fastlane.lanes" name="0: default_platform" time="0.000218">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.291087">
</testcase>
<testcase classname="fastlane.lanes" name="2: build_app" time="87.202089">
</testcase>
<testcase classname="fastlane.lanes" name="3: upload_to_testflight" time="12.706053">
<failure message="/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in &apos;Fastlane::Actions.execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:255:in &apos;block in Fastlane::Runner#execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in &apos;Dir.chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in &apos;Fastlane::Runner#execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:157:in &apos;Fastlane::Runner#trigger_action_by_name&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/fast_file.rb:159:in &apos;Fastlane::FastFile#method_missing&apos;&#10;Fastfile:23:in &apos;block (2 levels) in Fastlane::FastFile#parsing_binding&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane.rb:41:in &apos;Fastlane::Lane#call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:49:in &apos;block in Fastlane::Runner#execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in &apos;Dir.chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in &apos;Fastlane::Runner#execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane_manager.rb:46:in &apos;Fastlane::LaneManager.cruise_lane&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/command_line_handler.rb:34:in &apos;Fastlane::CommandLineHandler.handle&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:110:in &apos;block (2 levels) in Fastlane::CommandsGenerator#run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in &apos;Commander::Command#call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in &apos;Commander::Command#run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in &apos;Commander::Runner#run_active_command&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in &apos;Commander::Runner#run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in &apos;Commander::Delegates#run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:363:in &apos;Fastlane::CommandsGenerator#run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:43:in &apos;Fastlane::CommandsGenerator.start&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in &apos;Fastlane::CLIToolsDistributor.take_off&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/bin/fastlane:23:in &apos;&lt;top (required)&gt;&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in &apos;Kernel#load&apos;&#10;/opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in &apos;&lt;main&gt;&apos;&#10;&#10;Error uploading ipa file: &#10; [Application Loader Error Output]: The call to the altool completed with a non-zero exit status: 1. This indicates a failure." />
</testcase> </testcase>

View File

@@ -19,10 +19,11 @@ enum MessageTextType {
} }
} }
/// Message type (contact or channel) /// Message type (contact, channel, or system)
enum MessageType { enum MessageType {
contact, contact,
channel, channel,
system, // System messages (log entries, status updates)
} }
/// Message delivery status /// Message delivery status
@@ -103,6 +104,9 @@ class Message {
/// Check if message is from a contact /// Check if message is from a contact
bool get isContactMessage => messageType == MessageType.contact; bool get isContactMessage => messageType == MessageType.contact;
/// Check if message is a system message
bool get isSystemMessage => messageType == MessageType.system;
/// Get friendly time since message was sent /// Get friendly time since message was sent
String get timeAgo { String get timeAgo {
final diff = DateTime.now().difference(sentAt); final diff = DateTime.now().difference(sentAt);

View File

@@ -30,6 +30,9 @@ class MessagesProvider with ChangeNotifier {
List<Message> get sarMarkerMessages => List<Message> get sarMarkerMessages =>
_messages.where((m) => m.isSarMarker).toList(); _messages.where((m) => m.isSarMarker).toList();
List<Message> get systemMessages =>
_messages.where((m) => m.isSystemMessage).toList();
List<SarMarker> get sarMarkers => _sarMarkers.values.toList(); List<SarMarker> get sarMarkers => _sarMarkers.values.toList();
List<SarMarker> get foundPersonMarkers => List<SarMarker> get foundPersonMarkers =>
@@ -348,10 +351,39 @@ class MessagesProvider with ChangeNotifier {
'contact': contactMessages.length, 'contact': contactMessages.length,
'channel': channelMessages.length, 'channel': channelMessages.length,
'sar': sarMarkerMessages.length, 'sar': sarMarkerMessages.length,
'system': systemMessages.length,
'sarMarkers': sarMarkers.length, 'sarMarkers': sarMarkers.length,
}; };
} }
/// Log a system message (replaces toast notifications)
void logSystemMessage({
required String text,
String level = 'info', // 'info', 'success', 'warning', 'error'
}) {
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
final messageId = '${DateTime.now().millisecondsSinceEpoch}_system_$level';
final systemMessage = Message(
id: messageId,
messageType: MessageType.system,
pathLen: 0,
textType: MessageTextType.plain,
senderTimestamp: timestamp,
text: text,
receivedAt: DateTime.now(),
senderName: level, // Use senderName to store log level
deliveryStatus: MessageDeliveryStatus.received,
);
_messages.add(systemMessage);
// Don't persist system messages to reduce storage
// _persistMessages();
notifyListeners();
}
/// Get SAR marker statistics /// Get SAR marker statistics
Map<String, int> get sarMarkerStats { Map<String, int> get sarMarkerStats {
return { return {

View File

@@ -49,13 +49,21 @@ class _ContactsTabState extends State<ContactsTab> {
} }
/// Calculate distance between two points in meters /// Calculate distance between two points in meters
double _calculateDistanceInMeters(double lat1, double lon1, double lat2, double lon2) { double _calculateDistanceInMeters(
double lat1,
double lon1,
double lat2,
double lon2,
) {
const R = 6371000; // Earth's radius in meters const R = 6371000; // Earth's radius in meters
final dLat = (lat2 - lat1) * pi / 180; final dLat = (lat2 - lat1) * pi / 180;
final dLon = (lon2 - lon1) * pi / 180; final dLon = (lon2 - lon1) * pi / 180;
final a = sin(dLat / 2) * sin(dLat / 2) + final a =
cos(lat1 * pi / 180) * cos(lat2 * pi / 180) * sin(dLat / 2) * sin(dLat / 2) +
sin(dLon / 2) * sin(dLon / 2); cos(lat1 * pi / 180) *
cos(lat2 * pi / 180) *
sin(dLon / 2) *
sin(dLon / 2);
final c = 2 * atan2(sqrt(a), sqrt(1 - a)); final c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c; return R * c;
} }
@@ -110,52 +118,58 @@ class _ContactsTabState extends State<ContactsTab> {
child: ListView( child: ListView(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
children: [ children: [
// Team Members (Chat contacts) // Team Members (Chat contacts)
if (chatContacts.isNotEmpty) ...[ if (chatContacts.isNotEmpty) ...[
_SectionHeader( _SectionHeader(
title: 'Team Members', title: 'Team Members',
count: chatContacts.length, count: chatContacts.length,
icon: Icons.people, icon: Icons.people,
), ),
...chatContacts.map((contact) => ContactTile( ...chatContacts.map(
contact: contact, (contact) => ContactTile(
currentPosition: _currentPosition, contact: contact,
calculateDistance: _calculateDistanceInMeters, currentPosition: _currentPosition,
formatDistance: _formatDistance, calculateDistance: _calculateDistanceInMeters,
)), formatDistance: _formatDistance,
const Divider(height: 32), ),
], ),
const Divider(height: 32),
],
// Repeaters // Repeaters
if (repeaters.isNotEmpty) ...[ if (repeaters.isNotEmpty) ...[
_SectionHeader( _SectionHeader(
title: 'Repeaters', title: 'Repeaters',
count: repeaters.length, count: repeaters.length,
icon: Icons.router, icon: Icons.router,
), ),
...repeaters.map((contact) => ContactTile( ...repeaters.map(
contact: contact, (contact) => ContactTile(
currentPosition: _currentPosition, contact: contact,
calculateDistance: _calculateDistanceInMeters, currentPosition: _currentPosition,
formatDistance: _formatDistance, calculateDistance: _calculateDistanceInMeters,
)), formatDistance: _formatDistance,
const Divider(height: 32), ),
], ),
const Divider(height: 32),
],
// Rooms/Channels // Rooms/Channels
if (rooms.isNotEmpty) ...[ if (rooms.isNotEmpty) ...[
_SectionHeader( _SectionHeader(
title: 'Rooms/Channels', title: 'Rooms',
count: rooms.length, count: rooms.length,
icon: Icons.tag, icon: Icons.tag,
), ),
...rooms.map((contact) => ContactTile( ...rooms.map(
contact: contact, (contact) => ContactTile(
currentPosition: _currentPosition, contact: contact,
calculateDistance: _calculateDistanceInMeters, currentPosition: _currentPosition,
formatDistance: _formatDistance, calculateDistance: _calculateDistanceInMeters,
)), formatDistance: _formatDistance,
], ),
),
],
], ],
), ),
); );
@@ -185,9 +199,9 @@ class _SectionHeader extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
title, title,
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: Theme.of(
fontWeight: FontWeight.bold, context,
), ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Container( Container(

View File

@@ -11,6 +11,7 @@ import 'map_management_screen.dart';
import 'settings_screen.dart'; import 'settings_screen.dart';
import 'device_config_screen.dart'; import 'device_config_screen.dart';
import 'packet_log_screen.dart'; import 'packet_log_screen.dart';
import '../utils/toast_logger.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
final Function(AppThemeMode) onThemeChanged; final Function(AppThemeMode) onThemeChanged;
@@ -52,9 +53,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
if (!connectionProvider.deviceInfo.isConnected) { if (!connectionProvider.deviceInfo.isConnected) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Device not connected');
const SnackBar(content: Text('Device not connected')),
);
} }
return; return;
} }
@@ -64,12 +63,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) { if (!serviceEnabled) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Location services are disabled. Please enable them in Settings.');
const SnackBar(
content: Text('Location services are disabled. Please enable them in Settings.'),
duration: Duration(seconds: 3),
),
);
} }
return; return;
} }
@@ -80,12 +74,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
permission = await Geolocator.requestPermission(); permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Location permission denied');
const SnackBar(
content: Text('Location permission denied'),
duration: Duration(seconds: 2),
),
);
} }
return; return;
} }
@@ -93,12 +82,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
if (permission == LocationPermission.deniedForever) { if (permission == LocationPermission.deniedForever) {
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Location permission permanently denied. Please enable in Settings.');
const SnackBar(
content: Text('Location permission permanently denied. Please enable in Settings.'),
duration: Duration(seconds: 3),
),
);
} }
return; return;
} }
@@ -115,9 +99,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
} catch (e) { } catch (e) {
print('❌ Failed to get GPS position: $e'); print('❌ Failed to get GPS position: $e');
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Failed to get GPS location');
const SnackBar(content: Text('Failed to get GPS location')),
);
} }
return; return;
} }
@@ -135,21 +117,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
await connectionProvider.sendSelfAdvert(floodMode: true); await connectionProvider.sendSelfAdvert(floodMode: true);
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.success(context, 'Advertised at ${position.latitude.toStringAsFixed(6)}, ${position.longitude.toStringAsFixed(6)}');
SnackBar(
content: Text(
'Advertised at ${position.latitude.toStringAsFixed(6)}, ${position.longitude.toStringAsFixed(6)}',
),
duration: const Duration(seconds: 2),
),
);
} }
} catch (e) { } catch (e) {
print('❌ Failed to advertise device: $e'); print('❌ Failed to advertise device: $e');
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Failed to advertise: $e');
SnackBar(content: Text('Failed to advertise: $e')),
);
} }
} }
} }
@@ -720,9 +693,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
final appProvider = context.read<AppProvider>(); final appProvider = context.read<AppProvider>();
await appProvider.refresh(); await appProvider.refresh();
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.success(context, 'Refreshed contacts');
const SnackBar(content: Text('Refreshed contacts')),
);
} }
}, },
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),

View File

@@ -10,6 +10,7 @@ import '../providers/connection_provider.dart';
import '../models/message.dart'; import '../models/message.dart';
import '../models/sar_marker.dart'; import '../models/sar_marker.dart';
import '../widgets/messages/sar_update_sheet.dart'; import '../widgets/messages/sar_update_sheet.dart';
import '../utils/toast_logger.dart';
class MessagesTab extends StatefulWidget { class MessagesTab extends StatefulWidget {
final VoidCallback onNavigateToMap; final VoidCallback onNavigateToMap;
@@ -63,12 +64,7 @@ class _MessagesTabState extends State<MessagesTab> {
if (!connectionProvider.deviceInfo.isConnected) { if (!connectionProvider.deviceInfo.isConnected) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Not connected to device');
const SnackBar(
content: Text('Not connected to device'),
backgroundColor: Colors.red,
),
);
return; return;
} }
@@ -109,21 +105,10 @@ class _MessagesTabState extends State<MessagesTab> {
_focusNode.unfocus(); _focusNode.unfocus();
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.success(context, 'Message sent to public channel');
const SnackBar(
content: Text('Message sent to public channel'),
backgroundColor: Colors.green,
duration: Duration(seconds: 1),
),
);
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Failed to send: $e');
SnackBar(
content: Text('Failed to send: $e'),
backgroundColor: Colors.red,
),
);
} }
} }
@@ -153,23 +138,13 @@ class _MessagesTabState extends State<MessagesTab> {
if (!connectionProvider.deviceInfo.isConnected) { if (!connectionProvider.deviceInfo.isConnected) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Not connected to device');
const SnackBar(
content: Text('Not connected to device'),
backgroundColor: Colors.red,
),
);
return; return;
} }
if (!sendToChannel && roomPublicKey == null) { if (!sendToChannel && roomPublicKey == null) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Please select a room to send SAR marker');
const SnackBar(
content: Text('Please select a room to send SAR marker'),
backgroundColor: Colors.red,
),
);
return; return;
} }
@@ -190,13 +165,7 @@ class _MessagesTabState extends State<MessagesTab> {
); );
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.warning(context, '${sarType.displayName} marker broadcast to public channel');
SnackBar(
content: Text('${sarType.displayName} marker broadcast to public channel'),
backgroundColor: Colors.orange,
duration: const Duration(seconds: 2),
),
);
} else { } else {
// Create message ID // Create message ID
final messageId = '${DateTime.now().millisecondsSinceEpoch}_sent'; final messageId = '${DateTime.now().millisecondsSinceEpoch}_sent';
@@ -245,22 +214,11 @@ class _MessagesTabState extends State<MessagesTab> {
} }
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.success(context, '${sarType.displayName} marker sent to room');
SnackBar(
content: Text('${sarType.displayName} marker sent to room'),
backgroundColor: Colors.green,
duration: const Duration(seconds: 2),
),
);
} }
} catch (e) { } catch (e) {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Failed to send SAR marker: $e');
SnackBar(
content: Text('Failed to send SAR marker: $e'),
backgroundColor: Colors.red,
),
);
} }
} }
@@ -313,6 +271,12 @@ class _MessagesTabState extends State<MessagesTab> {
itemCount: messages.length, itemCount: messages.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final message = messages[index]; final message = messages[index];
// Display system messages with minimal styling
if (message.isSystemMessage) {
return _SystemMessageBubble(message: message);
}
return _MessageBubble( return _MessageBubble(
message: message, message: message,
onTap: message.isSarMarker && onTap: message.isSarMarker &&
@@ -438,12 +402,7 @@ class _MessageBubble extends StatelessWidget {
final messagesProvider = context.read<MessagesProvider>(); final messagesProvider = context.read<MessagesProvider>();
if (!connectionProvider.deviceInfo.isConnected) { if (!connectionProvider.deviceInfo.isConnected) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Not connected to device');
const SnackBar(
content: Text('Not connected to device'),
backgroundColor: Colors.red,
),
);
return; return;
} }
@@ -465,12 +424,7 @@ class _MessageBubble extends StatelessWidget {
// Direct message retry (for SAR markers sent to rooms) // Direct message retry (for SAR markers sent to rooms)
if (failedMessage.recipientPublicKey == null) { if (failedMessage.recipientPublicKey == null) {
messagesProvider.markMessageFailed(retryMessageId); messagesProvider.markMessageFailed(retryMessageId);
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Cannot retry: recipient information missing');
const SnackBar(
content: Text('Cannot retry: recipient information missing'),
backgroundColor: Colors.red,
),
);
return; return;
} }
@@ -491,20 +445,9 @@ class _MessageBubble extends StatelessWidget {
if (!sentSuccessfully) { if (!sentSuccessfully) {
messagesProvider.markMessageFailed(retryMessageId); messagesProvider.markMessageFailed(retryMessageId);
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Failed to resend message');
const SnackBar(
content: Text('Failed to resend message'),
backgroundColor: Colors.red,
),
);
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.info(context, 'Retrying message...');
const SnackBar(
content: Text('Retrying message...'),
backgroundColor: Colors.orange,
duration: Duration(seconds: 2),
),
);
} }
} else if (failedMessage.messageType == MessageType.channel) { } else if (failedMessage.messageType == MessageType.channel) {
// Channel message retry // Channel message retry
@@ -514,21 +457,10 @@ class _MessageBubble extends StatelessWidget {
messageId: retryMessageId, messageId: retryMessageId,
); );
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.info(context, 'Retrying message...');
const SnackBar(
content: Text('Retrying message...'),
backgroundColor: Colors.orange,
duration: const Duration(seconds: 2),
),
);
} }
} catch (e) { } catch (e) {
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.error(context, 'Retry failed: $e');
SnackBar(
content: Text('Retry failed: $e'),
backgroundColor: Colors.red,
),
);
} }
} }
@@ -551,12 +483,7 @@ class _MessageBubble extends StatelessWidget {
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: message.text)); Clipboard.setData(ClipboardData(text: message.text));
Navigator.pop(context); Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.success(context, 'Text copied to clipboard');
const SnackBar(
content: Text('Text copied to clipboard'),
duration: Duration(seconds: 1),
),
);
}, },
), ),
// Delete message option // Delete message option
@@ -590,12 +517,7 @@ class _MessageBubble extends StatelessWidget {
final messagesProvider = context.read<MessagesProvider>(); final messagesProvider = context.read<MessagesProvider>();
messagesProvider.deleteMessage(message.id); messagesProvider.deleteMessage(message.id);
Navigator.pop(context); Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar( ToastLogger.info(context, 'Message deleted');
const SnackBar(
content: Text('Message deleted'),
duration: Duration(seconds: 1),
),
);
}, },
style: TextButton.styleFrom(foregroundColor: Colors.red), style: TextButton.styleFrom(foregroundColor: Colors.red),
child: const Text('Delete'), child: const Text('Delete'),
@@ -953,3 +875,85 @@ class _MessageBubble extends StatelessWidget {
} }
} }
/// System message bubble - compact log-style display
class _SystemMessageBubble extends StatelessWidget {
final Message message;
const _SystemMessageBubble({required this.message});
Color _getLevelColor(String? level) {
switch (level?.toLowerCase()) {
case 'success':
return Colors.green;
case 'warning':
return Colors.orange;
case 'error':
return Colors.red;
case 'info':
default:
return Colors.blue.shade300;
}
}
IconData _getLevelIcon(String? level) {
switch (level?.toLowerCase()) {
case 'success':
return Icons.check_circle_outline;
case 'warning':
return Icons.warning_amber_outlined;
case 'error':
return Icons.error_outline;
case 'info':
default:
return Icons.info_outline;
}
}
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final level = message.senderName ?? 'info';
final levelColor = _getLevelColor(level);
return Container(
margin: const EdgeInsets.only(bottom: 2),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: isDarkMode
? levelColor.withValues(alpha: 0.1)
: levelColor.withValues(alpha: 0.05),
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
Icon(
_getLevelIcon(level),
size: 14,
color: levelColor,
),
const SizedBox(width: 6),
Text(
message.timeAgo,
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.6),
fontSize: 10,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
message.text,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 11,
color: Theme.of(context).textTheme.bodySmall?.color?.withValues(alpha: 0.8),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/messages_provider.dart';
/// Toast logger utility - replaces SnackBar with system messages in the channels tab
class ToastLogger {
/// Log an info message
static void info(BuildContext context, String message) {
_log(context, message, 'info');
}
/// Log a success message
static void success(BuildContext context, String message) {
_log(context, message, 'success');
}
/// Log a warning message
static void warning(BuildContext context, String message) {
_log(context, message, 'warning');
}
/// Log an error message
static void error(BuildContext context, String message) {
_log(context, message, 'error');
}
/// Internal method to log a system message
static void _log(BuildContext context, String message, String level) {
try {
final messagesProvider = context.read<MessagesProvider>();
messagesProvider.logSystemMessage(text: message, level: level);
} catch (e) {
// Fallback to print if provider is not available
debugPrint('[$level] $message');
}
}
}

View File

@@ -95,8 +95,25 @@ class ContactTile extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
// Connection type indicator (direct/flood) // Battery indicator
if (contact.type == ContactType.chat) ...[ if (battery != null) ...[
Icon(
_getBatteryIcon(battery),
size: 16,
color: _getBatteryColor(battery),
),
const SizedBox(width: 4),
Text(
'${battery.round()}%',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: _getBatteryColor(battery),
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 8),
],
// Connection type indicator (direct/flood) - shown for all contact types
if (contact.type != ContactType.channel) ...[
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -129,23 +146,6 @@ class ContactTile extends StatelessWidget {
], ],
), ),
), ),
const SizedBox(width: 8),
],
// Battery indicator on the right
if (battery != null) ...[
Icon(
_getBatteryIcon(battery),
size: 16,
color: _getBatteryColor(battery),
),
const SizedBox(width: 4),
Text(
'${battery.round()}%',
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: _getBatteryColor(battery),
fontWeight: FontWeight.w600,
),
),
], ],
], ],
), ),