mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Fixes
This commit is contained in:
@@ -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
BIN
ios/Runner.app.dSYM.zip
Normal file
Binary file not shown.
BIN
ios/Runner.ipa
BIN
ios/Runner.ipa
Binary file not shown.
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 'Fastlane::Actions.execute_action' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:255:in 'block in Fastlane::Runner#execute_action' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in 'Dir.chdir' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:229:in 'Fastlane::Runner#execute_action' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:157:in 'Fastlane::Runner#trigger_action_by_name' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/fast_file.rb:159:in 'Fastlane::FastFile#method_missing' Fastfile:23:in 'block (2 levels) in Fastlane::FastFile#parsing_binding' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane.rb:41:in 'Fastlane::Lane#call' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:49:in 'block in Fastlane::Runner#execute' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in 'Dir.chdir' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/runner.rb:45:in 'Fastlane::Runner#execute' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/lane_manager.rb:46:in 'Fastlane::LaneManager.cruise_lane' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/command_line_handler.rb:34:in 'Fastlane::CommandLineHandler.handle' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:110:in 'block (2 levels) in Fastlane::CommandsGenerator#run' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in 'Commander::Command#call' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in 'Commander::Command#run' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in 'Commander::Runner#run_active_command' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in 'Commander::Runner#run!' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in 'Commander::Delegates#run!' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:363:in 'Fastlane::CommandsGenerator#run' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/commands_generator.rb:43:in 'Fastlane::CommandsGenerator.start' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in 'Fastlane::CLIToolsDistributor.take_off' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/gems/fastlane-2.228.0/bin/fastlane:23:in '<top (required)>' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in 'Kernel#load' /opt/homebrew/Cellar/fastlane/2.228.0/libexec/bin/fastlane:25:in '<main>' Error uploading ipa file: [Application Loader Error Output]: The call to the altool completed with a non-zero exit status: 1. This indicates a failure." />
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
37
lib/utils/toast_logger.dart
Normal file
37
lib/utils/toast_logger.dart
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user