mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-17 07:56:28 +10:00
Merge branch 'main' into community-#-names
This commit is contained in:
@@ -67,5 +67,14 @@
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
<!-- URL launcher intents for opening links -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="http"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<data android:scheme="https"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
platform :ios, '12.0'
|
||||
platform :ios, '15.5'
|
||||
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
PODS:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_blue_plus_darwin (0.0.2):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- flutter_foreground_task (0.0.1):
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- GoogleDataTransport (10.1.0):
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- GoogleMLKit/BarcodeScanning (7.0.0):
|
||||
- GoogleMLKit/MLKitCore
|
||||
- MLKitBarcodeScanning (~> 6.0.0)
|
||||
- GoogleMLKit/MLKitCore (7.0.0):
|
||||
- MLKitCommon (~> 12.0.0)
|
||||
- GoogleToolboxForMac/Defines (4.2.1)
|
||||
- GoogleToolboxForMac/Logger (4.2.1):
|
||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
|
||||
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||
- GoogleUtilities/Environment (8.1.0):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.1.0)
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GTMSessionFetcher/Core (3.5.0)
|
||||
- MLImage (1.0.0-beta6)
|
||||
- MLKitBarcodeScanning (6.0.0):
|
||||
- MLKitCommon (~> 12.0)
|
||||
- MLKitVision (~> 8.0)
|
||||
- MLKitCommon (12.0.0):
|
||||
- GoogleDataTransport (~> 10.0)
|
||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||
- GoogleUtilities/Logger (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||
- MLKitVision (8.0.0):
|
||||
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||
- MLImage (= 1.0.0-beta6)
|
||||
- MLKitCommon (~> 12.0)
|
||||
- mobile_scanner (6.0.2):
|
||||
- Flutter
|
||||
- GoogleMLKit/BarcodeScanning (~> 7.0.0)
|
||||
- nanopb (3.30910.0):
|
||||
- nanopb/decode (= 3.30910.0)
|
||||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.4.0)
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_blue_plus_darwin (from `.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||
- flutter_foreground_task (from `.symlinks/plugins/flutter_foreground_task/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- GoogleDataTransport
|
||||
- GoogleMLKit
|
||||
- GoogleToolboxForMac
|
||||
- GoogleUtilities
|
||||
- GTMSessionFetcher
|
||||
- MLImage
|
||||
- MLKitBarcodeScanning
|
||||
- MLKitCommon
|
||||
- MLKitVision
|
||||
- nanopb
|
||||
- PromisesObjC
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_blue_plus_darwin:
|
||||
:path: ".symlinks/plugins/flutter_blue_plus_darwin/darwin"
|
||||
flutter_foreground_task:
|
||||
:path: ".symlinks/plugins/flutter_foreground_task/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||
flutter_foreground_task: a159d2c2173b33699ddb3e6c2a067045d7cebb89
|
||||
flutter_local_notifications: 395056b3175ba4f08480a7c5de30cd36d69827e4
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
|
||||
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
|
||||
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
|
||||
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
||||
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
||||
mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
|
||||
PODFILE CHECKSUM: 570da2a631486c6bd6496bed1e605e63e2471be5
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@@ -14,6 +14,7 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
9A698254711B63C3940A64CB /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4268181FCF3E12817B700E9C /* libPods-Runner.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -42,9 +43,13 @@
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
24A76623340E493BD4C25C5C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
40AC50CE3E1D4278E82498CF /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
4268181FCF3E12817B700E9C /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
718BC7DCCFC5C370705C12E5 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -62,6 +67,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9A698254711B63C3940A64CB /* libPods-Runner.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -94,6 +100,8 @@
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
DEE6F094D3B70E76087722E1 /* Pods */,
|
||||
DAE613E34DF694C2E33B64C7 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -121,6 +129,25 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DAE613E34DF694C2E33B64C7 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4268181FCF3E12817B700E9C /* libPods-Runner.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DEE6F094D3B70E76087722E1 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
40AC50CE3E1D4278E82498CF /* Pods-Runner.debug.xcconfig */,
|
||||
24A76623340E493BD4C25C5C /* Pods-Runner.release.xcconfig */,
|
||||
718BC7DCCFC5C370705C12E5 /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -145,12 +172,14 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
DE3B2E091393835C0B38492E /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
B788CEDB957A87EE8AC593BB /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -253,6 +282,45 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
B788CEDB957A87EE8AC593BB /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DE3B2E091393835C0B38492E /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -368,7 +436,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -384,7 +452,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -401,7 +469,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -416,7 +484,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen.RunnerTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
@@ -547,7 +615,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -569,7 +637,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.meshcoreOpen;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.monitormx.meshcoreopen;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
+3
@@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -55,5 +55,10 @@
|
||||
<string>This app uses Bluetooth to communicate with MeshCore devices.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app uses the camera to scan QR codes for joining communities.</string>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>http</string>
|
||||
<string>https</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -67,9 +67,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
final Map<int, List<ChannelMessage>> _channelMessages = {};
|
||||
final Set<String> _loadedConversationKeys = {};
|
||||
final Map<int, Set<String>> _processedChannelReactions =
|
||||
{}; // channelIndex -> Set of "reactionKey_emoji"
|
||||
{}; // channelIndex -> Set of "targetHash_emoji"
|
||||
final Map<String, Set<String>> _processedContactReactions =
|
||||
{}; // contactPubKeyHex -> Set of "reactionKey_emoji"
|
||||
{}; // contactPubKeyHex -> Set of "targetHash_emoji"
|
||||
|
||||
StreamSubscription<List<ScanResult>>? _scanSubscription;
|
||||
StreamSubscription<BluetoothConnectionState>? _connectionSubscription;
|
||||
@@ -146,6 +146,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
final Set<String> _knownContactKeys = {};
|
||||
final Map<String, int> _contactLastReadMs = {};
|
||||
final Map<int, int> _channelLastReadMs = {};
|
||||
bool _unreadStateLoaded = false;
|
||||
final Map<String, _RepeaterAckContext> _pendingRepeaterAcks = {};
|
||||
String? _activeContactKey;
|
||||
int? _activeChannelIndex;
|
||||
@@ -317,6 +318,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
}
|
||||
|
||||
int getUnreadCountForContactKey(String contactKeyHex) {
|
||||
if (!_unreadStateLoaded) return 0;
|
||||
if (!_shouldTrackUnreadForContactKey(contactKeyHex)) return 0;
|
||||
final messages = _conversations[contactKeyHex];
|
||||
if (messages == null || messages.isEmpty) return 0;
|
||||
@@ -336,6 +338,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
}
|
||||
|
||||
int getUnreadCountForChannelIndex(int channelIndex) {
|
||||
if (!_unreadStateLoaded) return 0;
|
||||
final messages = _channelMessages[channelIndex];
|
||||
if (messages == null || messages.isEmpty) return 0;
|
||||
final lastReadMs = _channelLastReadMs[channelIndex] ?? 0;
|
||||
@@ -350,6 +353,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
}
|
||||
|
||||
int getTotalUnreadCount() {
|
||||
if (!_unreadStateLoaded) return 0;
|
||||
var total = 0;
|
||||
// Count unread contact messages
|
||||
for (final contact in _contacts) {
|
||||
@@ -381,6 +385,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_channelLastReadMs
|
||||
..clear()
|
||||
..addAll(await _unreadStore.loadChannelLastRead());
|
||||
_unreadStateLoaded = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -620,6 +625,32 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_scanResults.clear();
|
||||
_setState(MeshCoreConnectionState.scanning);
|
||||
|
||||
// Ensure any previous scan is fully stopped
|
||||
await FlutterBluePlus.stopScan();
|
||||
await _scanSubscription?.cancel();
|
||||
|
||||
// On iOS/macOS, wait for Bluetooth to be powered on before scanning
|
||||
if (defaultTargetPlatform == TargetPlatform.iOS ||
|
||||
defaultTargetPlatform == TargetPlatform.macOS) {
|
||||
// Wait for adapter state to be powered on
|
||||
final adapterState = await FlutterBluePlus.adapterState.first;
|
||||
if (adapterState != BluetoothAdapterState.on) {
|
||||
// Wait for the adapter to turn on, with timeout
|
||||
await FlutterBluePlus.adapterState
|
||||
.firstWhere((state) => state == BluetoothAdapterState.on)
|
||||
.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () {
|
||||
_setState(MeshCoreConnectionState.disconnected);
|
||||
throw Exception('Bluetooth adapter not available');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Add a small delay to allow BLE stack to fully initialize
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
}
|
||||
|
||||
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||
_scanResults.clear();
|
||||
for (var result in results) {
|
||||
@@ -928,7 +959,12 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
if (!isConnected) return;
|
||||
if (_batteryRequested && !force) return;
|
||||
_batteryRequested = true;
|
||||
await sendFrame(buildGetBattAndStorageFrame());
|
||||
try {
|
||||
await sendFrame(buildGetBattAndStorageFrame());
|
||||
} catch (e) {
|
||||
// Connection likely lost - trigger disconnection handling
|
||||
_handleDisconnection();
|
||||
}
|
||||
}
|
||||
|
||||
void _startBatteryPolling() {
|
||||
@@ -1252,15 +1288,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
if (reactionInfo != null) {
|
||||
// Check if we've already processed this reaction
|
||||
_processedChannelReactions.putIfAbsent(channel.index, () => {});
|
||||
final reactionKey = reactionInfo.reactionKey;
|
||||
final reactionIdentifier = reactionKey != null
|
||||
? '${reactionKey}_${reactionInfo.emoji}'
|
||||
: null;
|
||||
final reactionIdentifier = '${reactionInfo.targetHash}_${reactionInfo.emoji}';
|
||||
|
||||
if (reactionIdentifier != null &&
|
||||
_processedChannelReactions[channel.index]!.contains(
|
||||
reactionIdentifier,
|
||||
)) {
|
||||
if (_processedChannelReactions[channel.index]!.contains(reactionIdentifier)) {
|
||||
// Already processed, don't process again
|
||||
return;
|
||||
}
|
||||
@@ -1274,9 +1304,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
await _channelMessageStore.saveChannelMessages(channel.index, messages);
|
||||
|
||||
// Mark this reaction as processed
|
||||
if (reactionIdentifier != null) {
|
||||
_processedChannelReactions[channel.index]!.add(reactionIdentifier);
|
||||
}
|
||||
_processedChannelReactions[channel.index]!.add(reactionIdentifier);
|
||||
|
||||
notifyListeners();
|
||||
|
||||
@@ -2652,26 +2680,20 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
// Parse reaction info
|
||||
final reactionInfo = Message.parseReaction(message.text);
|
||||
if (reactionInfo != null) {
|
||||
// Check if we've already processed this exact reaction using lightweight key
|
||||
// Check if we've already processed this exact reaction
|
||||
_processedContactReactions.putIfAbsent(pubKeyHex, () => {});
|
||||
final reactionKey = reactionInfo.reactionKey;
|
||||
final reactionIdentifier = reactionKey != null
|
||||
? '${reactionKey}_${reactionInfo.emoji}'
|
||||
: null;
|
||||
final reactionIdentifier = '${reactionInfo.targetHash}_${reactionInfo.emoji}';
|
||||
|
||||
final isDuplicate =
|
||||
reactionIdentifier != null &&
|
||||
_processedContactReactions[pubKeyHex]!.contains(reactionIdentifier);
|
||||
|
||||
if (!isDuplicate) {
|
||||
// New reaction - process it
|
||||
_processContactReaction(messages, reactionInfo);
|
||||
_processContactReaction(messages, reactionInfo, pubKeyHex);
|
||||
_messageStore.saveMessages(pubKeyHex, messages);
|
||||
|
||||
// Mark as processed
|
||||
if (reactionIdentifier != null) {
|
||||
_processedContactReactions[pubKeyHex]!.add(reactionIdentifier);
|
||||
}
|
||||
_processedContactReactions[pubKeyHex]!.add(reactionIdentifier);
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -2686,15 +2708,51 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
void _processContactReaction(
|
||||
List<Message> messages,
|
||||
ReactionInfo reactionInfo,
|
||||
String contactPubKeyHex,
|
||||
) {
|
||||
// Find target message by messageId
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
if (messages[i].messageId == reactionInfo.targetMessageId) {
|
||||
final currentReactions = Map<String, int>.from(messages[i].reactions);
|
||||
// Find target message by computing hash and comparing
|
||||
final targetHash = reactionInfo.targetHash;
|
||||
final contact = _contacts.cast<Contact?>().firstWhere(
|
||||
(c) => c?.publicKeyHex == contactPubKeyHex,
|
||||
orElse: () => null,
|
||||
);
|
||||
final isRoomServer = contact?.type == advTypeRoom;
|
||||
|
||||
for (int i = messages.length - 1; i >= 0; i--) {
|
||||
final msg = messages[i];
|
||||
|
||||
// For 1:1 chats: contact reacts to my outgoing messages only
|
||||
// For room servers: any message can be reacted to (multi-user)
|
||||
if (!isRoomServer && !msg.isOutgoing) continue;
|
||||
|
||||
final timestampSecs = msg.timestamp.millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
// For room servers, include sender name (resolve from fourByteRoomContactKey)
|
||||
// For 1:1 chats, sender is implicit (null)
|
||||
String? senderName;
|
||||
if (isRoomServer && !msg.isOutgoing) {
|
||||
// Resolve sender from the message's fourByteRoomContactKey
|
||||
final senderContact = _contacts.cast<Contact?>().firstWhere(
|
||||
(c) => c != null && _matchesPrefix(c.publicKey, msg.fourByteRoomContactKey),
|
||||
orElse: () => null,
|
||||
);
|
||||
senderName = senderContact?.name;
|
||||
} else if (isRoomServer && msg.isOutgoing) {
|
||||
senderName = selfName;
|
||||
}
|
||||
// For 1:1, senderName stays null
|
||||
|
||||
final msgHash = ReactionHelper.computeReactionHash(
|
||||
timestampSecs,
|
||||
senderName,
|
||||
msg.text,
|
||||
);
|
||||
if (msgHash == targetHash) {
|
||||
final currentReactions = Map<String, int>.from(msg.reactions);
|
||||
currentReactions[reactionInfo.emoji] =
|
||||
(currentReactions[reactionInfo.emoji] ?? 0) + 1;
|
||||
|
||||
messages[i] = messages[i].copyWith(reactions: currentReactions);
|
||||
messages[i] = msg.copyWith(reactions: currentReactions);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2845,18 +2903,12 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
// Parse reaction info
|
||||
final reactionInfo = ChannelMessage.parseReaction(message.text);
|
||||
if (reactionInfo != null) {
|
||||
// Check if we've already processed this exact reaction using lightweight key
|
||||
// Check if we've already processed this exact reaction
|
||||
_processedChannelReactions.putIfAbsent(channelIndex, () => {});
|
||||
final reactionKey = reactionInfo.reactionKey;
|
||||
final reactionIdentifier = reactionKey != null
|
||||
? '${reactionKey}_${reactionInfo.emoji}'
|
||||
: null;
|
||||
final reactionIdentifier = '${reactionInfo.targetHash}_${reactionInfo.emoji}';
|
||||
|
||||
final isDuplicate =
|
||||
reactionIdentifier != null &&
|
||||
_processedChannelReactions[channelIndex]!.contains(
|
||||
reactionIdentifier,
|
||||
);
|
||||
_processedChannelReactions[channelIndex]!.contains(reactionIdentifier);
|
||||
|
||||
if (!isDuplicate) {
|
||||
// New reaction - process it
|
||||
@@ -2865,9 +2917,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_channelMessageStore.saveChannelMessages(channelIndex, messages);
|
||||
|
||||
// Mark as processed
|
||||
if (reactionIdentifier != null) {
|
||||
_processedChannelReactions[channelIndex]!.add(reactionIdentifier);
|
||||
}
|
||||
_processedChannelReactions[channelIndex]!.add(reactionIdentifier);
|
||||
}
|
||||
return false; // Don't add reaction as a visible message
|
||||
}
|
||||
@@ -2963,14 +3013,22 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
List<ChannelMessage> messages,
|
||||
ReactionInfo reactionInfo,
|
||||
) {
|
||||
// Find target message by messageId
|
||||
for (int i = 0; i < messages.length; i++) {
|
||||
if (messages[i].messageId == reactionInfo.targetMessageId) {
|
||||
final currentReactions = Map<String, int>.from(messages[i].reactions);
|
||||
// Find target message by computing hash and comparing
|
||||
final targetHash = reactionInfo.targetHash;
|
||||
for (int i = messages.length - 1; i >= 0; i--) {
|
||||
final msg = messages[i];
|
||||
final timestampSecs = msg.timestamp.millisecondsSinceEpoch ~/ 1000;
|
||||
final msgHash = ReactionHelper.computeReactionHash(
|
||||
timestampSecs,
|
||||
msg.senderName,
|
||||
msg.text,
|
||||
);
|
||||
if (msgHash == targetHash) {
|
||||
final currentReactions = Map<String, int>.from(msg.reactions);
|
||||
currentReactions[reactionInfo.emoji] =
|
||||
(currentReactions[reactionInfo.emoji] ?? 0) + 1;
|
||||
|
||||
messages[i] = messages[i].copyWith(reactions: currentReactions);
|
||||
messages[i] = msg.copyWith(reactions: currentReactions);
|
||||
notifyListeners();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ class BufferReader {
|
||||
return data;
|
||||
}
|
||||
|
||||
void skipBytes(int count) {
|
||||
_pointer += count;
|
||||
}
|
||||
|
||||
Uint8List readRemainingBytes() => readBytes(remaining);
|
||||
|
||||
String readString() =>
|
||||
@@ -127,6 +131,7 @@ const int cmdSendStatusReq = 27;
|
||||
const int cmdGetContactByKey = 30;
|
||||
const int cmdGetChannel = 31;
|
||||
const int cmdSetChannel = 32;
|
||||
const int cmdSendTracePath = 36;
|
||||
const int cmdGetRadioSettings = 57;
|
||||
const int cmdGetTelemetryReq = 39;
|
||||
const int cmdGetCustomVar = 40;
|
||||
@@ -176,6 +181,7 @@ const int pushCodeLoginSuccess = 0x85;
|
||||
const int pushCodeLoginFail = 0x86;
|
||||
const int pushCodeStatusResponse = 0x87;
|
||||
const int pushCodeLogRxData = 0x88;
|
||||
const int pushCodeTraceData = 0x89;
|
||||
const int pushCodeNewAdvert = 0x8A;
|
||||
const int pushCodeTelemetryResponse = 0x8B;
|
||||
const int pushCodeBinaryResponse = 0x8C;
|
||||
@@ -195,8 +201,8 @@ const int maxFrameSize = 172;
|
||||
const int appProtocolVersion = 3;
|
||||
// Matches firmware MAX_TEXT_LEN (10 * CIPHER_BLOCK_SIZE).
|
||||
const int maxTextPayloadBytes = 160;
|
||||
const int _sendTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 6 + 1;
|
||||
const int _sendChannelTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 1;
|
||||
const int _sendTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 6 + 1 + 2; // +2 safety margin
|
||||
const int _sendChannelTextMsgOverheadBytes = 1 + 1 + 1 + 4 + 1 + 2; // +2 safety margin
|
||||
|
||||
int maxContactMessageBytes() {
|
||||
final byFrame = maxFrameSize - _sendTextMsgOverheadBytes;
|
||||
@@ -708,3 +714,18 @@ Uint8List buildSendBinaryReq(Uint8List repeaterPubKey, {Uint8List? payload}) {
|
||||
}
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
//Build a trace request frame
|
||||
//[cmd][tag x4][auth x4][flag][payload]
|
||||
Uint8List buildTraceReq(int tag, int auth, int flag, {Uint8List? payload})
|
||||
{
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSendTracePath);
|
||||
writer.writeUInt32LE(tag);
|
||||
writer.writeUInt32LE(auth);
|
||||
writer.writeByte(flag);
|
||||
if (payload != null && payload.isNotEmpty) {
|
||||
writer.writeBytes(payload);
|
||||
}
|
||||
return writer.toBytes();
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChatScrollController extends ScrollController {
|
||||
final ValueNotifier<bool> showJumpToBottom = ValueNotifier(false);
|
||||
VoidCallback? onScrollNearTop;
|
||||
|
||||
static const _bottomThreshold = 100.0;
|
||||
static const _topThreshold = 50.0;
|
||||
|
||||
ChatScrollController() {
|
||||
addListener(_handleScroll);
|
||||
}
|
||||
|
||||
void _handleScroll() {
|
||||
if (!hasClients) return;
|
||||
final pos = position;
|
||||
|
||||
// With reverse: true, position 0 is bottom, maxScrollExtent is top
|
||||
// Show jump button when scrolled away from bottom (position > threshold)
|
||||
final isAtBottom = pos.pixels <= _bottomThreshold;
|
||||
if (showJumpToBottom.value == isAtBottom) {
|
||||
showJumpToBottom.value = !isAtBottom;
|
||||
}
|
||||
|
||||
// Pagination trigger when scrolled near top (maxScrollExtent)
|
||||
if (pos.pixels >= pos.maxScrollExtent - _topThreshold) {
|
||||
onScrollNearTop?.call();
|
||||
}
|
||||
}
|
||||
|
||||
void jumpToBottom() {
|
||||
if (hasClients && position.maxScrollExtent > 0) {
|
||||
animateTo(
|
||||
0, // With reverse: true, position 0 is bottom
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyboardOpen() {
|
||||
// Simple: just scroll to bottom when keyboard opens
|
||||
if (hasClients) {
|
||||
animateTo(
|
||||
0, // With reverse: true, position 0 is bottom
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void scrollToBottomIfAtBottom() {
|
||||
// Only scroll if jump button is NOT showing (i.e., already at bottom)
|
||||
if (!showJumpToBottom.value && hasClients && position.maxScrollExtent > 0) {
|
||||
animateTo(
|
||||
0, // With reverse: true, position 0 is bottom
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
showJumpToBottom.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
|
||||
class LinkHandler {
|
||||
static Future<void> handleLinkTap(BuildContext context, String url) async {
|
||||
// Show confirmation dialog
|
||||
final shouldOpen = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.chat_openLink),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.chat_openLinkConfirmation,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SelectableText(
|
||||
url,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(context.l10n.common_cancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: Text(context.l10n.chat_open),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldOpen != true) return;
|
||||
|
||||
// Launch URL
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.chat_couldNotOpenLink(url)),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.chat_invalidLink),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,70 @@
|
||||
import '../widgets/emoji_picker.dart';
|
||||
|
||||
class ReactionInfo {
|
||||
final String targetMessageId;
|
||||
final String targetHash;
|
||||
final String emoji;
|
||||
final String? reactionKey; // Lightweight key for deduplication: timestamp_senderPrefix
|
||||
|
||||
ReactionInfo({
|
||||
required this.targetMessageId,
|
||||
required this.targetHash,
|
||||
required this.emoji,
|
||||
this.reactionKey,
|
||||
});
|
||||
}
|
||||
|
||||
class ReactionHelper {
|
||||
/// Parse reaction format: r:[messageId]:[emoji]
|
||||
/// Supports both old format (full messageId) and new format (timestamp_senderPrefix)
|
||||
static List<String>? _cachedEmojis;
|
||||
|
||||
/// Combined list of all reaction emojis in fixed order.
|
||||
/// Order must stay stable for index compatibility.
|
||||
static List<String> get reactionEmojis {
|
||||
return _cachedEmojis ??= [
|
||||
...EmojiPicker.quickEmojis,
|
||||
...EmojiPicker.smileys,
|
||||
...EmojiPicker.gestures,
|
||||
...EmojiPicker.hearts,
|
||||
...EmojiPicker.objects,
|
||||
];
|
||||
}
|
||||
|
||||
/// Convert emoji to 2-char hex index. Returns null if emoji not in list.
|
||||
static String? emojiToIndex(String emoji) {
|
||||
final idx = reactionEmojis.indexOf(emoji);
|
||||
if (idx < 0) return null;
|
||||
return idx.toRadixString(16).padLeft(2, '0');
|
||||
}
|
||||
|
||||
/// Convert 2-char hex index to emoji. Returns null if invalid index.
|
||||
static String? indexToEmoji(String hexIndex) {
|
||||
final idx = int.tryParse(hexIndex, radix: 16);
|
||||
if (idx == null || idx < 0 || idx >= reactionEmojis.length) return null;
|
||||
return reactionEmojis[idx];
|
||||
}
|
||||
|
||||
/// Compute a 4-char hex hash for a message reaction.
|
||||
/// Hash input: timestampSeconds + [senderName] + first 5 chars of text
|
||||
/// For 1:1 chats, senderName can be null (sender is implicit).
|
||||
static String computeReactionHash(int timestampSeconds, String? senderName, String text) {
|
||||
final first5 = text.length >= 5 ? text.substring(0, 5) : text;
|
||||
final input = senderName != null
|
||||
? '$timestampSeconds$senderName$first5'
|
||||
: '$timestampSeconds$first5';
|
||||
// Use hashCode and take lower 16 bits, format as 4 hex chars
|
||||
final hash = input.hashCode & 0xFFFF;
|
||||
return hash.toRadixString(16).padLeft(4, '0');
|
||||
}
|
||||
|
||||
/// Parse reaction format: r:HASH:INDEX (where INDEX is 2-char hex emoji index)
|
||||
/// Returns null if text is not a valid reaction format
|
||||
static ReactionInfo? parseReaction(String text) {
|
||||
final regex = RegExp(r'^r:([^:]+):(.+)$');
|
||||
final regex = RegExp(r'^r:([0-9a-f]{4}):([0-9a-f]{2})$');
|
||||
final match = regex.firstMatch(text);
|
||||
if (match == null) return null;
|
||||
|
||||
final targetId = match.group(1)!;
|
||||
final emoji = match.group(2)!;
|
||||
|
||||
// Extract reaction key for deduplication
|
||||
// If targetId is in new format (timestamp_senderPrefix), use it directly
|
||||
// Otherwise, extract timestamp from old format (timestamp_nameHash_textHash)
|
||||
String? reactionKey;
|
||||
if (targetId.contains('_')) {
|
||||
final parts = targetId.split('_');
|
||||
if (parts.length >= 2) {
|
||||
// New format: timestamp_senderPrefix, or old format with at least timestamp
|
||||
reactionKey = '${parts[0]}_${parts[1]}';
|
||||
}
|
||||
}
|
||||
final emoji = indexToEmoji(match.group(2)!);
|
||||
if (emoji == null) return null;
|
||||
|
||||
return ReactionInfo(
|
||||
targetMessageId: targetId,
|
||||
targetHash: match.group(1)!,
|
||||
emoji: emoji,
|
||||
reactionKey: reactionKey,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate a lightweight reaction key for a message
|
||||
/// Format: r:[timestamp]_[senderPrefix]:[emoji]
|
||||
static String buildReactionText(String timestamp, String senderPrefix, String emoji) {
|
||||
return 'r:${timestamp}_$senderPrefix:$emoji';
|
||||
}
|
||||
|
||||
/// Extract sender prefix from public key hex (first 8 chars)
|
||||
static String getSenderPrefix(String senderKeyHex) {
|
||||
return senderKeyHex.substring(0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Отваряне на връзката?",
|
||||
"chat_openLinkConfirmation": "Искате ли да отворите тази връзка в браузъра си?",
|
||||
"chat_open": "Отвори",
|
||||
"chat_couldNotOpenLink": "Не можа да се отвори връзката: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Невалиден формат на връзката",
|
||||
"map_title": "Карта на възлите",
|
||||
"map_noNodesWithLocation": "Няма възли с данни за местоположение.",
|
||||
"map_nodesNeedGps": "Възлагат се възлозите да споделят техните GPS координати,\nза да се появят на картата.",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Това ще изтрие също {count} канал(а) и техните съобщения.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Остави общността \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Общ хаштаг (всеки може да се присъедини)",
|
||||
"community_communityHashtag": "Общностен хаштаг",
|
||||
"community_communityHashtagDesc": "Само за членове на общността",
|
||||
"community_forCommunity": "За {name}"
|
||||
"community_forCommunity": "За {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecretConfirm": "Регенерация на секретния ключ за \"{name}\"? Всички членове ще трябва да сканират новия QR код, за да продължат комуникацията.",
|
||||
"community_secretRegenerated": "Секретно презареждане за \"{name}\"",
|
||||
"community_regenerateSecret": "Регенерейрай секрет",
|
||||
"community_regenerate": "Регенерация",
|
||||
"community_updateSecret": "Актуализирай тайна",
|
||||
"community_scanToUpdateSecret": "Сканьорвайте новия QR код, за да актуализирате секрета за \"{name}\"",
|
||||
"community_secretUpdated": "Секретно обновено за \"{name}\"",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Вие",
|
||||
"pathTrace_notAvailable": "Пътека за проследяване не е достъпна.",
|
||||
"contacts_pathTrace": "Пътен проследяване",
|
||||
"pathTrace_refreshTooltip": "Обнови Path Trace.",
|
||||
"pathTrace_failed": "Пътят за проследяване не успя.",
|
||||
"contacts_repeaterPing": "Пингване на повторителя",
|
||||
"contacts_repeaterPathTrace": "Трасировка до повторител",
|
||||
"contacts_ping": "Пинг",
|
||||
"contacts_chatTraceRoute": "Трасиране на път",
|
||||
"contacts_roomPathTrace": "Трасиране на път до съ",
|
||||
"contacts_roomPing": "Ping на сървъра на стаята",
|
||||
"contacts_pathTraceTo": "Проследи маршрут към {name}"
|
||||
}
|
||||
|
||||
+77
-9
@@ -74,7 +74,7 @@
|
||||
"settings_title": "Einstellungen",
|
||||
"settings_deviceInfo": "Geräteinformationen",
|
||||
"settings_appSettings": "App-Einstellungen",
|
||||
"settings_appSettingsSubtitle": "Benachrichtigungen, Messaging und Kartenwahrnehmungen",
|
||||
"settings_appSettingsSubtitle": "Benachrichtigungen, Messaging und Kartenwahrnehmung",
|
||||
"settings_nodeSettings": "Knoten-Einstellungen",
|
||||
"settings_nodeName": "Knotenname",
|
||||
"settings_nodeNameNotSet": "Nicht festgelegt",
|
||||
@@ -266,7 +266,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_manageRepeater": "Wiederholungen verwalten",
|
||||
"contacts_manageRepeater": "Repeater verwalten",
|
||||
"contacts_roomLogin": "Raum-Login",
|
||||
"contacts_openChat": "Öffne Chat",
|
||||
"contacts_editGroup": "Gruppe bearbeiten",
|
||||
@@ -360,7 +360,7 @@
|
||||
"channels_channelIndexLabel": "Kanalindex",
|
||||
"channels_channelName": "Kanalname",
|
||||
"channels_usePublicChannel": "Verwende öffentlichen Kanal",
|
||||
"channels_standardPublicPsk": "Standard-Öffentliche PSK",
|
||||
"channels_standardPublicPsk": "Öffentliche Standard PSK",
|
||||
"channels_pskHex": "PSK (Hex)",
|
||||
"channels_generateRandomPsk": "Zufällige PSK generieren",
|
||||
"channels_enterChannelName": "Bitte geben Sie einen Kanalnamen ein.",
|
||||
@@ -489,8 +489,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"debugFrame_textMessageHeader": "Textnachricht-Frame:",
|
||||
"debugFrame_destinationPubKey": "- Ziel-Pub-Schlüssel: {pubKey}",
|
||||
"debugFrame_textMessageHeader": "Textnachrichten Frame:",
|
||||
"debugFrame_destinationPubKey": "- Ziel-Public-Schlüssel: {pubKey}",
|
||||
"@debugFrame_destinationPubKey": {
|
||||
"placeholders": {
|
||||
"pubKey": {
|
||||
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Link öffnen?",
|
||||
"chat_openLinkConfirmation": "Möchten Sie diesen Link in Ihrem Browser öffnen?",
|
||||
"chat_open": "Öffnen",
|
||||
"chat_couldNotOpenLink": "Link konnte nicht geöffnet werden: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Ungültiges Link-Format",
|
||||
"map_title": "Karte",
|
||||
"map_noNodesWithLocation": "Keine Knoten mit Standortdaten",
|
||||
"map_nodesNeedGps": "Knoten müssen ihre GPS-Koordinaten teilen,\num auf der Karte zu erscheinen.",
|
||||
@@ -1014,7 +1026,7 @@
|
||||
"repeater_encryptedAdvertInterval": "Intervall der verschlüsselten Ankündigung",
|
||||
"repeater_dangerZone": "Gefahrenzone",
|
||||
"repeater_rebootRepeater": "Neustart Repeater",
|
||||
"repeater_rebootRepeaterSubtitle": "Wiederholen Sie das Repeater-Gerät.",
|
||||
"repeater_rebootRepeaterSubtitle": "Repeater-Gerät neu starten.",
|
||||
"repeater_rebootRepeaterConfirm": "Sind Sie sicher, dass Sie diesen Repeater neu starten möchten?",
|
||||
"repeater_regenerateIdentityKey": "Schlüssel für die Identitätswiederherstellung",
|
||||
"repeater_regenerateIdentityKeySubtitle": "Neuen öffentlichen/privaten Schlüsselpaar generieren",
|
||||
@@ -1349,7 +1361,7 @@
|
||||
"neighbors_receivedData": "Empfangene Nachbarendaten",
|
||||
"neighbors_requestTimedOut": "Nachbarn melden zeitweise Ausfall.",
|
||||
"neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}",
|
||||
"neighbors_repeatersNeighbours": "Wiederholer Nachbarn",
|
||||
"neighbors_repeatersNeighbours": "Nachbarn",
|
||||
"neighbors_noData": "Keine Nachbardaten verfügbar.",
|
||||
"channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei",
|
||||
"channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Dies löscht auch {count} Kanal/Kanäle und deren Nachrichten.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Community \"{name}\" verlassen",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Öffentliches Hashtag (jeder kann teilnehmen)",
|
||||
"community_communityHashtagDesc": "Nur für Mitglieder der Community",
|
||||
"community_forCommunity": "Für {name}",
|
||||
"community_communityHashtag": "Community Hashtag"
|
||||
"community_communityHashtag": "Community Hashtag",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerate": "Neu generieren",
|
||||
"community_secretRegenerated": "Wiederherstellung des Schlüssels für \"{name}\" erfolgreich",
|
||||
"community_regenerateSecretConfirm": "Nehmen Sie den geheimen Schlüssel für \"{name}\" neu auf? Alle Mitglieder müssen den neuen QR-Code scannen, um die Kommunikation fortzusetzen.",
|
||||
"community_regenerateSecret": "Neugenerierung des Schlüssels",
|
||||
"community_secretUpdated": "Schlüssel für \"{name}\" aktualisiert",
|
||||
"community_scanToUpdateSecret": "Scannen Sie den neuen QR-Code, um das Geheimnis für \"{name}\" zu aktualisieren.",
|
||||
"community_updateSecret": "Aktualisieren Sie den Schlüssel",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_refreshTooltip": "Path Trace aktualisieren.",
|
||||
"pathTrace_you": "Du",
|
||||
"pathTrace_failed": "Pfadverfolgung fehlgeschlagen.",
|
||||
"pathTrace_notAvailable": "Pfadverfolgung nicht verfügbar.",
|
||||
"contacts_pathTrace": "Pfadverfolgung",
|
||||
"contacts_ping": "Pingen",
|
||||
"contacts_repeaterPathTrace": "Pfadverfolgung zum Repeater",
|
||||
"contacts_repeaterPing": "Repeater pingen",
|
||||
"contacts_roomPathTrace": "Pfadverfolgung zum Raumserver",
|
||||
"contacts_roomPing": "Raumserver anpingen",
|
||||
"contacts_pathTraceTo": "Route nach {name} verfolgen",
|
||||
"contacts_chatTraceRoute": "Pfadverfolgungsroute"
|
||||
}
|
||||
|
||||
+32
-1
@@ -174,6 +174,8 @@
|
||||
"appSettings_languageNl": "Nederlands",
|
||||
"appSettings_languageSk": "Slovenčina",
|
||||
"appSettings_languageBg": "Български",
|
||||
"appSettings_languageRu": "Русский",
|
||||
"appSettings_languageUk": "Українська",
|
||||
"appSettings_notifications": "Notifications",
|
||||
"appSettings_enableNotifications": "Enable Notifications",
|
||||
"appSettings_enableNotificationsSubtitle": "Receive notifications for messages and adverts",
|
||||
@@ -550,6 +552,16 @@
|
||||
"count": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Open Link?",
|
||||
"chat_openLinkConfirmation": "Do you want to open this link in your browser?",
|
||||
"chat_open": "Open",
|
||||
"chat_couldNotOpenLink": "Could not open link: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Invalid link format",
|
||||
|
||||
"map_title": "Node Map",
|
||||
"map_noNodesWithLocation": "No nodes with location data",
|
||||
@@ -1298,5 +1310,24 @@
|
||||
"listFilter_repeaters": "Repeaters",
|
||||
"listFilter_roomServers": "Room servers",
|
||||
"listFilter_unreadOnly": "Unread only",
|
||||
"listFilter_newGroup": "New group"
|
||||
"listFilter_newGroup": "New group",
|
||||
|
||||
"pathTrace_you": "You",
|
||||
"pathTrace_failed": "Path trace failed.",
|
||||
"pathTrace_notAvailable": "Path trace not available.",
|
||||
"pathTrace_refreshTooltip": "Refresh Path Trace.",
|
||||
"contacts_pathTrace": "Path Trace",
|
||||
"contacts_ping": "Ping",
|
||||
"contacts_repeaterPathTrace": "Path trace to repeater",
|
||||
"contacts_repeaterPing": "Ping repeater",
|
||||
"contacts_roomPathTrace": "Path trace to room server",
|
||||
"contacts_roomPing": "Ping room server",
|
||||
"contacts_chatTraceRoute": "Path trace route",
|
||||
"contacts_pathTraceTo": "Trace route to {name}",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {"type": "String"}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "¿Abrir enlace?",
|
||||
"chat_openLinkConfirmation": "¿Quiere abrir este enlace en su navegador?",
|
||||
"chat_open": "Abrir",
|
||||
"chat_couldNotOpenLink": "No se pudo abrir el enlace: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Formato de enlace no válido",
|
||||
"map_title": "Mapa de Nodos",
|
||||
"map_noNodesWithLocation": "No hay nodos con datos de ubicación",
|
||||
"map_nodesNeedGps": "Los nodos necesitan compartir sus coordenadas GPS\npara aparecer en el mapa",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Esto también eliminará {count} canal(es) y sus mensajes.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Has salido de la comunidad \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Hashtag público (cualquiera puede unirse)",
|
||||
"community_communityHashtag": "Hashtag de la Comunidad",
|
||||
"community_communityHashtagDesc": "Exclusivo para miembros de la comunidad",
|
||||
"community_forCommunity": "Para {name}"
|
||||
"community_forCommunity": "Para {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecret": "Regenerar Contraseña Secreta",
|
||||
"community_regenerateSecretConfirm": "Regenerar la clave secreta para \"{name}\"? Todos los miembros deberán escanear el nuevo código QR para seguir comunicándose.",
|
||||
"community_secretRegenerated": "Código secreto regenerado para \"{name}\"",
|
||||
"community_regenerate": "Regenerar",
|
||||
"community_secretUpdated": "Confidencialidad actualizada para \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Escanear el nuevo código QR para actualizar el secreto de \"{name}\"",
|
||||
"community_updateSecret": "Actualizar Contraseña",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Tú",
|
||||
"pathTrace_failed": "El trazado de ruta falló.",
|
||||
"pathTrace_refreshTooltip": "Actualizar Path Trace",
|
||||
"contacts_pathTrace": "Rastreo de caminos",
|
||||
"contacts_repeaterPathTrace": "Rastrear ruta al repetidor",
|
||||
"contacts_repeaterPing": "Pingar repetidor",
|
||||
"contacts_ping": "Ping",
|
||||
"pathTrace_notAvailable": "El trazado de ruta no está disponible.",
|
||||
"contacts_roomPing": "Pingar servidor de sala",
|
||||
"contacts_roomPathTrace": "Rastreo de ruta al servidor de la habitación",
|
||||
"contacts_pathTraceTo": "Rastrear ruta a {name}",
|
||||
"contacts_chatTraceRoute": "Ruta de trazado"
|
||||
}
|
||||
|
||||
+96
-28
@@ -104,7 +104,7 @@
|
||||
"settings_timeSynchronized": "Synchronisation temporelle",
|
||||
"settings_refreshContacts": "Rafraîchir les Contacts",
|
||||
"settings_refreshContactsSubtitle": "Recharger la liste des contacts depuis l'appareil",
|
||||
"settings_rebootDevice": "Réinitialiser l'appareil",
|
||||
"settings_rebootDevice": "Redémarrer l'appareil",
|
||||
"settings_rebootDeviceSubtitle": "Redémarrer l'appareil MeshCore",
|
||||
"settings_rebootDeviceConfirm": "Êtes-vous sûr de vouloir redémarrer l'appareil ? Vous serez déconnecté.",
|
||||
"settings_debug": "Déboguer",
|
||||
@@ -279,7 +279,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_newGroup": "Nouvelle Groupe",
|
||||
"contacts_newGroup": "Nouveau Groupe",
|
||||
"contacts_groupName": "Nom du groupe",
|
||||
"contacts_groupNameRequired": "Le nom du groupe est obligatoire.",
|
||||
"contacts_groupAlreadyExists": "Le groupe \"{name}\" existe déjà.",
|
||||
@@ -293,8 +293,8 @@
|
||||
"contacts_filterContacts": "Filtrer les contacts...",
|
||||
"contacts_noContactsMatchFilter": "Aucun contact ne correspond à votre filtre.",
|
||||
"contacts_noMembers": "Aucun membre",
|
||||
"contacts_lastSeenNow": "Dernière fois vu maintenant",
|
||||
"contacts_lastSeenMinsAgo": "Dernière fois vu il y a {minutes} minutes.",
|
||||
"contacts_lastSeenNow": "Vu maintenant",
|
||||
"contacts_lastSeenMinsAgo": "Vu il y a {minutes} minutes",
|
||||
"@contacts_lastSeenMinsAgo": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
@@ -302,8 +302,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_lastSeenHourAgo": "Dernière fois vu il y a 1 heure.",
|
||||
"contacts_lastSeenHoursAgo": "Dernière fois vu il y a {hours} heures.",
|
||||
"contacts_lastSeenHourAgo": "Vu il y a 1 heure",
|
||||
"contacts_lastSeenHoursAgo": "Vu il y a {hours} heures",
|
||||
"@contacts_lastSeenHoursAgo": {
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
@@ -311,8 +311,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_lastSeenDayAgo": "Dernière fois vu il y a 1 jour",
|
||||
"contacts_lastSeenDaysAgo": "Dernière activité il y a {days} jours",
|
||||
"contacts_lastSeenDayAgo": "Vu il y a 1 jour",
|
||||
"contacts_lastSeenDaysAgo": "Vu il y a {days} jours",
|
||||
"@contacts_lastSeenDaysAgo": {
|
||||
"placeholders": {
|
||||
"days": {
|
||||
@@ -394,7 +394,7 @@
|
||||
"channels_sortBy": "Trier par",
|
||||
"channels_sortManual": "Manuel",
|
||||
"channels_sortAZ": "A à Z",
|
||||
"channels_sortLatestMessages": "Dernières messages",
|
||||
"channels_sortLatestMessages": "Derniers messages",
|
||||
"channels_sortUnread": "Non lu",
|
||||
"chat_noMessages": "Aucun message pour le moment.",
|
||||
"chat_sendMessageToStart": "Envoyer un message pour commencer",
|
||||
@@ -436,7 +436,7 @@
|
||||
"chat_messageCopied": "Message copié",
|
||||
"chat_messageDeleted": "Message supprimé",
|
||||
"chat_retryingMessage": "Tentative de récupération.",
|
||||
"chat_retryCount": "Réessayer {current}/{max}",
|
||||
"chat_retryCount": "Essai {current}/{max}",
|
||||
"@chat_retryCount": {
|
||||
"placeholders": {
|
||||
"current": {
|
||||
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Ouvrir le lien ?",
|
||||
"chat_openLinkConfirmation": "Voulez-vous ouvrir ce lien dans votre navigateur ?",
|
||||
"chat_open": "Ouvrir",
|
||||
"chat_couldNotOpenLink": "Impossible d'ouvrir le lien : {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Format de lien invalide",
|
||||
"map_title": "Carte des nœuds",
|
||||
"map_noNodesWithLocation": "Aucun nœud avec des données de localisation",
|
||||
"map_nodesNeedGps": "Les nœuds doivent partager leurs coordonnées GPS\npour apparaître sur la carte.",
|
||||
@@ -687,7 +699,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapCache_cachedTilesWithFailed": "Tiles mis en cache ({downloaded}) ({failed} ratés)",
|
||||
"mapCache_cachedTilesWithFailed": "Tuiles mis en cache ({downloaded}) ({failed} ratés)",
|
||||
"@mapCache_cachedTilesWithFailed": {
|
||||
"placeholders": {
|
||||
"downloaded": {
|
||||
@@ -734,7 +746,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mapCache_boundsLabel": "N {north}, S {south}, E {east}, W {west}",
|
||||
"mapCache_boundsLabel": "N {north}, S {south}, E {east}, O {west}",
|
||||
"@mapCache_boundsLabel": {
|
||||
"placeholders": {
|
||||
"north": {
|
||||
@@ -751,7 +763,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"time_justNow": "Il y a tout juste maintenant",
|
||||
"time_justNow": "Maintenant",
|
||||
"time_minutesAgo": "{minutes} minutes auparavant",
|
||||
"@time_minutesAgo": {
|
||||
"placeholders": {
|
||||
@@ -899,7 +911,7 @@
|
||||
"repeater_packetStatistics": "Statistiques des paquets",
|
||||
"repeater_sent": "Envoyé",
|
||||
"repeater_received": "Reçu",
|
||||
"repeater_duplicates": "Dupliques",
|
||||
"repeater_duplicates": "Doublons",
|
||||
"repeater_daysHoursMinsSecs": "{days} jours {hours}h {minutes}m {seconds}s",
|
||||
"@repeater_daysHoursMinsSecs": {
|
||||
"placeholders": {
|
||||
@@ -1012,7 +1024,7 @@
|
||||
}
|
||||
},
|
||||
"repeater_encryptedAdvertInterval": "Intervalle d'annonces cryptées",
|
||||
"repeater_dangerZone": "Zone d'alerte",
|
||||
"repeater_dangerZone": "Zone dangereuse",
|
||||
"repeater_rebootRepeater": "Redémarrer Répéteur",
|
||||
"repeater_rebootRepeaterSubtitle": "Réinitialiser l'appareil répétiteur",
|
||||
"repeater_rebootRepeaterConfirm": "Êtes-vous sûr de vouloir redémarrer ce répétiteur ?",
|
||||
@@ -1108,7 +1120,7 @@
|
||||
"repeater_cliHelpSetAf": "Définit le facteur de temps d'air.",
|
||||
"repeater_cliHelpSetTx": "Définit la puissance de transmission LoRa en dBm (réinitialisation requise pour appliquer).",
|
||||
"repeater_cliHelpSetRepeat": "Active ou désactive le rôle du répétiteur pour ce nœud.",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Serveur de pièce) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Room server) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)",
|
||||
"repeater_cliHelpSetFloodMax": "Définit le nombre maximal de sauts pour les paquets de balayage entrants (si >= max, le paquet n'est pas acheminé).",
|
||||
"repeater_cliHelpSetIntThresh": "Définit le seuil d'interférence (en dB). La valeur par défaut est de 14. Définir sur 0 désactive la détection des interférences de canal.",
|
||||
"repeater_cliHelpSetAgcResetInterval": "Définit l'intervalle pour réinitialiser le contrôleur de gain automatique. Mettez à 0 pour désactiver.",
|
||||
@@ -1327,16 +1339,16 @@
|
||||
"channelPath_unknownRepeater": "Répéteur Inconnu",
|
||||
"listFilter_tooltip": "Filtrer et trier",
|
||||
"listFilter_sortBy": "Trier par",
|
||||
"listFilter_latestMessages": "Dernières messages",
|
||||
"listFilter_latestMessages": "Derniers messages",
|
||||
"listFilter_heardRecently": "Écoute récemment",
|
||||
"listFilter_az": "A à Z",
|
||||
"listFilter_filters": "Filtres",
|
||||
"listFilter_all": "Tout",
|
||||
"listFilter_users": "Utilisateurs",
|
||||
"listFilter_repeaters": "Répéteurs",
|
||||
"listFilter_roomServers": "Serveurs de pièce",
|
||||
"listFilter_roomServers": "Room servers",
|
||||
"listFilter_unreadOnly": "Messages non lus seulement",
|
||||
"listFilter_newGroup": "Nouvelle groupe",
|
||||
"listFilter_newGroup": "Nouveau groupe",
|
||||
"@neighbors_errorLoading": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
@@ -1362,7 +1374,7 @@
|
||||
"channels_scanQrCode": "Scanner un code QR",
|
||||
"channels_scanQrCodeComingSoon": "Bientôt disponible",
|
||||
"channels_enterHashtag": "Entrez le hashtag",
|
||||
"channels_hashtagHint": "ex. #équipe",
|
||||
"channels_hashtagHint": "ex. #equipe",
|
||||
"@neighbors_unknownContact": {
|
||||
"placeholders": {
|
||||
"pubkey": {
|
||||
@@ -1379,11 +1391,11 @@
|
||||
},
|
||||
"neighbors_unknownContact": "Clé publique inconnue {pubkey}",
|
||||
"neighbors_heardAgo": "Écouté : {time} auparavant",
|
||||
"settings_locationGPSEnable": "Habilita GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Habilita la actualización automática de la ubicación mediante GPS.",
|
||||
"settings_locationIntervalSec": "Intervalo pour GPS (Segundos)",
|
||||
"settings_locationIntervalInvalid": "El intervalo debe ser de al menos 60 segundos y menor que 86400 segundos.",
|
||||
"contacts_manageRoom": "Gestionar Servidor de Habitación",
|
||||
"settings_locationGPSEnable": "Activer le GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Activer la mise à jour automatique de la position via GPS",
|
||||
"settings_locationIntervalSec": "Intervalle de mise-à-jour du GPS (Secondes)",
|
||||
"settings_locationIntervalInvalid": "L'intervalle doit être compris entre 60 et 86400 secondes.",
|
||||
"contacts_manageRoom": "Gérer le Room Server",
|
||||
"room_management": "Administración del Servidor de Habitación",
|
||||
"@community_joinConfirmation": {
|
||||
"placeholders": {
|
||||
@@ -1473,16 +1485,72 @@
|
||||
"community_deleteChannelsWarning": "Cela supprimera également {count} canal/canaux et leurs messages.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Communauté \"{name}\" quittée",
|
||||
"community_addHashtagChannel": "Ajouter un Hashtag Communauté",
|
||||
"community_addHashtagChannelDesc": "Ajouter un canal hachage pour cette communauté",
|
||||
"community_addHashtagChannelDesc": "Ajouter un canal hashtag pour cette communauté",
|
||||
"community_selectCommunity": "Sélectionner Communauté",
|
||||
"community_regularHashtag": "Hashtag régulier",
|
||||
"community_regularHashtagDesc": "Hashtag public (tout le monde peut rejoindre)",
|
||||
"community_communityHashtag": "Hashtag de la communauté",
|
||||
"community_communityHashtagDesc": "Exclusif aux membres de la communauté",
|
||||
"community_forCommunity": "Pour {name}"
|
||||
"community_forCommunity": "Pour {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecret": "Régénérer le secret",
|
||||
"community_regenerateSecretConfirm": "Régénérer la clé secrète pour \"{name}\" ? Tous les membres devront scanner le nouveau code QR pour continuer à communiquer.",
|
||||
"community_regenerate": "Régénérer",
|
||||
"community_secretRegenerated": "Mot de passe secret régénéré pour \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Scanner le nouveau code QR pour mettre à jour le mot de passe pour \"{name}\"",
|
||||
"community_updateSecret": "Mettre à jour le secret",
|
||||
"community_secretUpdated": "Modification secrète mise à jour pour \"{name}\"",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Vous",
|
||||
"pathTrace_refreshTooltip": "Actualiser Path Trace",
|
||||
"pathTrace_failed": "Traçage du chemin échoué.",
|
||||
"pathTrace_notAvailable": "Tracé de chemin non disponible.",
|
||||
"contacts_pathTrace": "Traçage de chemin",
|
||||
"contacts_repeaterPathTrace": "Tracer le chemin vers le répéteur",
|
||||
"contacts_repeaterPing": "Pinguer le répéteur",
|
||||
"contacts_roomPathTrace": "Traçage du chemin vers le serveur de la salle",
|
||||
"contacts_chatTraceRoute": "Tracer le chemin",
|
||||
"contacts_pathTraceTo": "Tracer l'itinéraire vers {name}",
|
||||
"contacts_ping": "Ping",
|
||||
"contacts_roomPing": "Pinguer le serveur de la salle"
|
||||
}
|
||||
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Aprire il link?",
|
||||
"chat_openLinkConfirmation": "Vuoi aprire questo link nel tuo browser?",
|
||||
"chat_open": "Apri",
|
||||
"chat_couldNotOpenLink": "Impossibile aprire il link: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Formato di link non valido",
|
||||
"map_title": "Mappa Nodi",
|
||||
"map_noNodesWithLocation": "Nessun nodo con dati di posizione",
|
||||
"map_nodesNeedGps": "I nodi devono condividere le loro coordinate GPS\nper apparire sulla mappa",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Questo eliminerà anche {count} canale/i e i loro messaggi.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Hai lasciato la comunità \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Hashtag pubblico (chiunque può unirsi)",
|
||||
"community_communityHashtag": "Hashtag della Comunità",
|
||||
"community_communityHashtagDesc": "Visibile solo ai membri della comunità",
|
||||
"community_forCommunity": "Per {name}"
|
||||
"community_forCommunity": "Per {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecretConfirm": "Regenera la chiave segreta per \"{name}\"? Tutti i membri dovranno scansionare il nuovo codice QR per continuare a comunicare.",
|
||||
"community_regenerateSecret": "Ri genera la chiave segreta",
|
||||
"community_regenerate": "Rigenera",
|
||||
"community_secretRegenerated": "Codice segreto rigenerato per \"{name}\"",
|
||||
"community_updateSecret": "Aggiorna Segreto",
|
||||
"community_secretUpdated": "Segreto aggiornato per \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Scansiona il nuovo codice QR per aggiornare il segreto di \"{name}\"",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_failed": "Tracciamento del percorso fallito.",
|
||||
"pathTrace_you": "Tu",
|
||||
"pathTrace_notAvailable": "Tracciamento del percorso non disponibile.",
|
||||
"pathTrace_refreshTooltip": "Aggiorna Path Trace.",
|
||||
"contacts_ping": "Ping",
|
||||
"contacts_repeaterPathTrace": "Traccia percorso al ripetitore",
|
||||
"contacts_roomPathTrace": "Traccia del percorso al server della stanza",
|
||||
"contacts_pathTrace": "Traccia Percorso",
|
||||
"contacts_repeaterPing": "Ripetitore ping",
|
||||
"contacts_pathTraceTo": "Traccia percorso verso {name}",
|
||||
"contacts_roomPing": "Ping al server della stanza",
|
||||
"contacts_chatTraceRoute": "Traccia percorso path"
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ import 'app_localizations_it.dart';
|
||||
import 'app_localizations_nl.dart';
|
||||
import 'app_localizations_pl.dart';
|
||||
import 'app_localizations_pt.dart';
|
||||
import 'app_localizations_ru.dart';
|
||||
import 'app_localizations_sk.dart';
|
||||
import 'app_localizations_sl.dart';
|
||||
import 'app_localizations_sv.dart';
|
||||
import 'app_localizations_uk.dart';
|
||||
import 'app_localizations_zh.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
@@ -114,9 +116,11 @@ abstract class AppLocalizations {
|
||||
Locale('nl'),
|
||||
Locale('pl'),
|
||||
Locale('pt'),
|
||||
Locale('ru'),
|
||||
Locale('sk'),
|
||||
Locale('sl'),
|
||||
Locale('sv'),
|
||||
Locale('uk'),
|
||||
Locale('zh'),
|
||||
];
|
||||
|
||||
@@ -942,6 +946,18 @@ abstract class AppLocalizations {
|
||||
/// **'Български'**
|
||||
String get appSettings_languageBg;
|
||||
|
||||
/// No description provided for @appSettings_languageRu.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Русский'**
|
||||
String get appSettings_languageRu;
|
||||
|
||||
/// No description provided for @appSettings_languageUk.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Українська'**
|
||||
String get appSettings_languageUk;
|
||||
|
||||
/// No description provided for @appSettings_notifications.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -2226,6 +2242,36 @@ abstract class AppLocalizations {
|
||||
/// **'Unread: {count}'**
|
||||
String chat_unread(int count);
|
||||
|
||||
/// No description provided for @chat_openLink.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open Link?'**
|
||||
String get chat_openLink;
|
||||
|
||||
/// No description provided for @chat_openLinkConfirmation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Do you want to open this link in your browser?'**
|
||||
String get chat_openLinkConfirmation;
|
||||
|
||||
/// No description provided for @chat_open.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open'**
|
||||
String get chat_open;
|
||||
|
||||
/// No description provided for @chat_couldNotOpenLink.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Could not open link: {url}'**
|
||||
String chat_couldNotOpenLink(String url);
|
||||
|
||||
/// No description provided for @chat_invalidLink.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid link format'**
|
||||
String get chat_invalidLink;
|
||||
|
||||
/// No description provided for @map_title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -4653,6 +4699,78 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'New group'**
|
||||
String get listFilter_newGroup;
|
||||
|
||||
/// No description provided for @pathTrace_you.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You'**
|
||||
String get pathTrace_you;
|
||||
|
||||
/// No description provided for @pathTrace_failed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Path trace failed.'**
|
||||
String get pathTrace_failed;
|
||||
|
||||
/// No description provided for @pathTrace_notAvailable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Path trace not available.'**
|
||||
String get pathTrace_notAvailable;
|
||||
|
||||
/// No description provided for @pathTrace_refreshTooltip.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Refresh Path Trace.'**
|
||||
String get pathTrace_refreshTooltip;
|
||||
|
||||
/// No description provided for @contacts_pathTrace.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Path Trace'**
|
||||
String get contacts_pathTrace;
|
||||
|
||||
/// No description provided for @contacts_ping.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ping'**
|
||||
String get contacts_ping;
|
||||
|
||||
/// No description provided for @contacts_repeaterPathTrace.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Path trace to repeater'**
|
||||
String get contacts_repeaterPathTrace;
|
||||
|
||||
/// No description provided for @contacts_repeaterPing.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ping repeater'**
|
||||
String get contacts_repeaterPing;
|
||||
|
||||
/// No description provided for @contacts_roomPathTrace.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Path trace to room server'**
|
||||
String get contacts_roomPathTrace;
|
||||
|
||||
/// No description provided for @contacts_roomPing.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ping room server'**
|
||||
String get contacts_roomPing;
|
||||
|
||||
/// No description provided for @contacts_chatTraceRoute.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Path trace route'**
|
||||
String get contacts_chatTraceRoute;
|
||||
|
||||
/// No description provided for @contacts_pathTraceTo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Trace route to {name}'**
|
||||
String contacts_pathTraceTo(String name);
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
@@ -4675,9 +4793,11 @@ class _AppLocalizationsDelegate
|
||||
'nl',
|
||||
'pl',
|
||||
'pt',
|
||||
'ru',
|
||||
'sk',
|
||||
'sl',
|
||||
'sv',
|
||||
'uk',
|
||||
'zh',
|
||||
].contains(locale.languageCode);
|
||||
|
||||
@@ -4706,12 +4826,16 @@ AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
return AppLocalizationsPl();
|
||||
case 'pt':
|
||||
return AppLocalizationsPt();
|
||||
case 'ru':
|
||||
return AppLocalizationsRu();
|
||||
case 'sk':
|
||||
return AppLocalizationsSk();
|
||||
case 'sl':
|
||||
return AppLocalizationsSl();
|
||||
case 'sv':
|
||||
return AppLocalizationsSv();
|
||||
case 'uk':
|
||||
return AppLocalizationsUk();
|
||||
case 'zh':
|
||||
return AppLocalizationsZh();
|
||||
}
|
||||
|
||||
@@ -450,6 +450,12 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Уведомления';
|
||||
|
||||
@@ -1207,6 +1213,24 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
return 'Непрочетени: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Отваряне на връзката?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Искате ли да отворите тази връзка в браузъра си?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Отвори';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Не можа да се отвори връзката: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Невалиден формат на връзката';
|
||||
|
||||
@override
|
||||
String get map_title => 'Карта на възлите';
|
||||
|
||||
@@ -2567,32 +2591,32 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Регенерейрай секрет';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Регенерация на секретния ключ за \"$name\"? Всички членове ще трябва да сканират новия QR код, за да продължат комуникацията.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Регенерация';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Секретно презареждане за \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Актуализирай тайна';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Секретно обновено за \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Сканьорвайте новия QR код, за да актуализирате секрета за \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2658,4 +2682,42 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Нова група';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Вие';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Пътят за проследяване не успя.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Пътека за проследяване не е достъпна.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Обнови Path Trace.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Пътен проследяване';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Пинг';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Трасировка до повторител';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Пингване на повторителя';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Трасиране на път до съ';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping на сървъра на стаята';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Трасиране на път';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Проследи маршрут към $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get settings_appSettingsSubtitle =>
|
||||
'Benachrichtigungen, Messaging und Kartenwahrnehmungen';
|
||||
'Benachrichtigungen, Messaging und Kartenwahrnehmung';
|
||||
|
||||
@override
|
||||
String get settings_nodeSettings => 'Knoten-Einstellungen';
|
||||
@@ -444,6 +444,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Benachrichtigungen';
|
||||
|
||||
@@ -662,7 +668,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Wiederholungen verwalten';
|
||||
String get contacts_manageRepeater => 'Repeater verwalten';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Raum-Server verwalten';
|
||||
@@ -796,7 +802,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get channels_usePublicChannel => 'Verwende öffentlichen Kanal';
|
||||
|
||||
@override
|
||||
String get channels_standardPublicPsk => 'Standard-Öffentliche PSK';
|
||||
String get channels_standardPublicPsk => 'Öffentliche Standard PSK';
|
||||
|
||||
@override
|
||||
String get channels_pskHex => 'PSK (Hex)';
|
||||
@@ -1029,11 +1035,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get debugFrame_textMessageHeader => 'Textnachricht-Frame:';
|
||||
String get debugFrame_textMessageHeader => 'Textnachrichten Frame:';
|
||||
|
||||
@override
|
||||
String debugFrame_destinationPubKey(String pubKey) {
|
||||
return '- Ziel-Pub-Schlüssel: $pubKey';
|
||||
return '- Ziel-Public-Schlüssel: $pubKey';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1206,6 +1212,24 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return 'Ungelesen: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Link öffnen?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Möchten Sie diesen Link in Ihrem Browser öffnen?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Öffnen';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Link konnte nicht geöffnet werden: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Ungültiges Link-Format';
|
||||
|
||||
@override
|
||||
String get map_title => 'Karte';
|
||||
|
||||
@@ -1870,8 +1894,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get repeater_rebootRepeater => 'Neustart Repeater';
|
||||
|
||||
@override
|
||||
String get repeater_rebootRepeaterSubtitle =>
|
||||
'Wiederholen Sie das Repeater-Gerät.';
|
||||
String get repeater_rebootRepeaterSubtitle => 'Repeater-Gerät neu starten.';
|
||||
|
||||
@override
|
||||
String get repeater_rebootRepeaterConfirm =>
|
||||
@@ -2339,7 +2362,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get neighbors_repeatersNeighbours => 'Wiederholer Nachbarn';
|
||||
String get neighbors_repeatersNeighbours => 'Nachbarn';
|
||||
|
||||
@override
|
||||
String get neighbors_noData => 'Keine Nachbardaten verfügbar.';
|
||||
@@ -2570,32 +2593,32 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Neugenerierung des Schlüssels';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Nehmen Sie den geheimen Schlüssel für \"$name\" neu auf? Alle Mitglieder müssen den neuen QR-Code scannen, um die Kommunikation fortzusetzen.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Neu generieren';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Wiederherstellung des Schlüssels für \"$name\" erfolgreich';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Aktualisieren Sie den Schlüssel';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Schlüssel für \"$name\" aktualisiert';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Scannen Sie den neuen QR-Code, um das Geheimnis für \"$name\" zu aktualisieren.';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2663,4 +2686,42 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Neue Gruppe';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Du';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Pfadverfolgung fehlgeschlagen.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Pfadverfolgung nicht verfügbar.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Path Trace aktualisieren.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Pfadverfolgung';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Pingen';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Pfadverfolgung zum Repeater';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Repeater pingen';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Pfadverfolgung zum Raumserver';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Raumserver anpingen';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Pfadverfolgungsroute';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Route nach $name verfolgen';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +442,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Notifications';
|
||||
|
||||
@@ -1186,6 +1192,24 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'Unread: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Open Link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Do you want to open this link in your browser?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Open';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Could not open link: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Invalid link format';
|
||||
|
||||
@override
|
||||
String get map_title => 'Node Map';
|
||||
|
||||
@@ -2618,4 +2642,42 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'New group';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'You';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Path trace failed.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Path trace not available.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Refresh Path Trace.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Path Trace';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Ping';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Path trace to repeater';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Ping repeater';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Path trace to room server';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping room server';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Path trace route';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Trace route to $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,6 +447,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Notificaciones';
|
||||
|
||||
@@ -1204,6 +1210,24 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
return 'Sin leer: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => '¿Abrir enlace?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'¿Quiere abrir este enlace en su navegador?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Abrir';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'No se pudo abrir el enlace: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Formato de enlace no válido';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa de Nodos';
|
||||
|
||||
@@ -2565,32 +2589,32 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Regenerar Contraseña Secreta';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Regenerar la clave secreta para \"$name\"? Todos los miembros deberán escanear el nuevo código QR para seguir comunicándose.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Regenerar';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Código secreto regenerado para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Actualizar Contraseña';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Confidencialidad actualizada para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Escanear el nuevo código QR para actualizar el secreto de \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2657,4 +2681,43 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nuevo grupo';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Tú';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'El trazado de ruta falló.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'El trazado de ruta no está disponible.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Actualizar Path Trace';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Rastreo de caminos';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Ping';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Rastrear ruta al repetidor';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Pingar repetidor';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace =>
|
||||
'Rastreo de ruta al servidor de la habitación';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Pingar servidor de sala';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Ruta de trazado';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Rastrear ruta a $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,18 +204,19 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get settings_locationInvalid => 'Latitude ou longitude invalide.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Habilita GPS';
|
||||
String get settings_locationGPSEnable => 'Activer le GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnableSubtitle =>
|
||||
'Habilita la actualización automática de la ubicación mediante GPS.';
|
||||
'Activer la mise à jour automatique de la position via GPS';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalSec => 'Intervalo pour GPS (Segundos)';
|
||||
String get settings_locationIntervalSec =>
|
||||
'Intervalle de mise-à-jour du GPS (Secondes)';
|
||||
|
||||
@override
|
||||
String get settings_locationIntervalInvalid =>
|
||||
'El intervalo debe ser de al menos 60 segundos y menor que 86400 segundos.';
|
||||
'L\'intervalle doit être compris entre 60 et 86400 secondes.';
|
||||
|
||||
@override
|
||||
String get settings_latitude => 'Latitude';
|
||||
@@ -272,7 +273,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Recharger la liste des contacts depuis l\'appareil';
|
||||
|
||||
@override
|
||||
String get settings_rebootDevice => 'Réinitialiser l\'appareil';
|
||||
String get settings_rebootDevice => 'Redémarrer l\'appareil';
|
||||
|
||||
@override
|
||||
String get settings_rebootDeviceSubtitle => 'Redémarrer l\'appareil MeshCore';
|
||||
@@ -447,6 +448,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Notifications';
|
||||
|
||||
@@ -667,7 +674,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get contacts_manageRepeater => 'Gérer le répétiteur';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Gestionar Servidor de Habitación';
|
||||
String get contacts_manageRoom => 'Gérer le Room Server';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Connexion Salle';
|
||||
@@ -687,7 +694,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_newGroup => 'Nouvelle Groupe';
|
||||
String get contacts_newGroup => 'Nouveau Groupe';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Nom du groupe';
|
||||
@@ -711,27 +718,27 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get contacts_noMembers => 'Aucun membre';
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenNow => 'Dernière fois vu maintenant';
|
||||
String get contacts_lastSeenNow => 'Vu maintenant';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenMinsAgo(int minutes) {
|
||||
return 'Dernière fois vu il y a $minutes minutes.';
|
||||
return 'Vu il y a $minutes minutes';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenHourAgo => 'Dernière fois vu il y a 1 heure.';
|
||||
String get contacts_lastSeenHourAgo => 'Vu il y a 1 heure';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenHoursAgo(int hours) {
|
||||
return 'Dernière fois vu il y a $hours heures.';
|
||||
return 'Vu il y a $hours heures';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenDayAgo => 'Dernière fois vu il y a 1 jour';
|
||||
String get contacts_lastSeenDayAgo => 'Vu il y a 1 jour';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenDaysAgo(int days) {
|
||||
return 'Dernière activité il y a $days jours';
|
||||
return 'Vu il y a $days jours';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -845,7 +852,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get channels_sortAZ => 'A à Z';
|
||||
|
||||
@override
|
||||
String get channels_sortLatestMessages => 'Dernières messages';
|
||||
String get channels_sortLatestMessages => 'Derniers messages';
|
||||
|
||||
@override
|
||||
String get channels_sortUnread => 'Non lu';
|
||||
@@ -888,7 +895,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get channels_enterHashtag => 'Entrez le hashtag';
|
||||
|
||||
@override
|
||||
String get channels_hashtagHint => 'ex. #équipe';
|
||||
String get channels_hashtagHint => 'ex. #equipe';
|
||||
|
||||
@override
|
||||
String get chat_noMessages => 'Aucun message pour le moment.';
|
||||
@@ -936,7 +943,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String chat_retryCount(int current, int max) {
|
||||
return 'Réessayer $current/$max';
|
||||
return 'Essai $current/$max';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1209,6 +1216,24 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
return 'Non lu : $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Ouvrir le lien ?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Voulez-vous ouvrir ce lien dans votre navigateur ?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Ouvrir';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Impossible d\'ouvrir le lien : $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Format de lien invalide';
|
||||
|
||||
@override
|
||||
String get map_title => 'Carte des nœuds';
|
||||
|
||||
@@ -1371,7 +1396,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String mapCache_cachedTilesWithFailed(int downloaded, int failed) {
|
||||
return 'Tiles mis en cache ($downloaded) ($failed ratés)';
|
||||
return 'Tuiles mis en cache ($downloaded) ($failed ratés)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1425,11 +1450,11 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String east,
|
||||
String west,
|
||||
) {
|
||||
return 'N $north, S $south, E $east, W $west';
|
||||
return 'N $north, S $south, E $east, O $west';
|
||||
}
|
||||
|
||||
@override
|
||||
String get time_justNow => 'Il y a tout juste maintenant';
|
||||
String get time_justNow => 'Maintenant';
|
||||
|
||||
@override
|
||||
String time_minutesAgo(int minutes) {
|
||||
@@ -1725,7 +1750,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get repeater_received => 'Reçu';
|
||||
|
||||
@override
|
||||
String get repeater_duplicates => 'Dupliques';
|
||||
String get repeater_duplicates => 'Doublons';
|
||||
|
||||
@override
|
||||
String repeater_daysHoursMinsSecs(
|
||||
@@ -1873,7 +1898,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
'Intervalle d\'annonces cryptées';
|
||||
|
||||
@override
|
||||
String get repeater_dangerZone => 'Zone d\'alerte';
|
||||
String get repeater_dangerZone => 'Zone dangereuse';
|
||||
|
||||
@override
|
||||
String get repeater_rebootRepeater => 'Redémarrer Répéteur';
|
||||
@@ -2070,7 +2095,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetAllowReadOnly =>
|
||||
'(Serveur de pièce) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)';
|
||||
'(Room server) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetFloodMax =>
|
||||
@@ -2581,32 +2606,32 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Régénérer le secret';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Régénérer la clé secrète pour \"$name\" ? Tous les membres devront scanner le nouveau code QR pour continuer à communiquer.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Régénérer';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Mot de passe secret régénéré pour \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Mettre à jour le secret';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Modification secrète mise à jour pour \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Scanner le nouveau code QR pour mettre à jour le mot de passe pour \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2614,7 +2639,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
'Ajouter un canal hachage pour cette communauté';
|
||||
'Ajouter un canal hashtag pour cette communauté';
|
||||
|
||||
@override
|
||||
String get community_selectCommunity => 'Sélectionner Communauté';
|
||||
@@ -2645,7 +2670,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get listFilter_sortBy => 'Trier par';
|
||||
|
||||
@override
|
||||
String get listFilter_latestMessages => 'Dernières messages';
|
||||
String get listFilter_latestMessages => 'Derniers messages';
|
||||
|
||||
@override
|
||||
String get listFilter_heardRecently => 'Écoute récemment';
|
||||
@@ -2666,11 +2691,50 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
String get listFilter_repeaters => 'Répéteurs';
|
||||
|
||||
@override
|
||||
String get listFilter_roomServers => 'Serveurs de pièce';
|
||||
String get listFilter_roomServers => 'Room servers';
|
||||
|
||||
@override
|
||||
String get listFilter_unreadOnly => 'Messages non lus seulement';
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nouvelle groupe';
|
||||
String get listFilter_newGroup => 'Nouveau groupe';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Vous';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Traçage du chemin échoué.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Tracé de chemin non disponible.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Actualiser Path Trace';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Traçage de chemin';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Ping';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Tracer le chemin vers le répéteur';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Pinguer le répéteur';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace =>
|
||||
'Traçage du chemin vers le serveur de la salle';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Pinguer le serveur de la salle';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Tracer le chemin';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Tracer l\'itinéraire vers $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,6 +446,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Notifiche';
|
||||
|
||||
@@ -1203,6 +1209,24 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
return 'Non letti: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Aprire il link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Vuoi aprire questo link nel tuo browser?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Apri';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Impossibile aprire il link: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Formato di link non valido';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mappa Nodi';
|
||||
|
||||
@@ -2565,32 +2589,32 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Ri genera la chiave segreta';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Regenera la chiave segreta per \"$name\"? Tutti i membri dovranno scansionare il nuovo codice QR per continuare a comunicare.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Rigenera';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Codice segreto rigenerato per \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Aggiorna Segreto';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Segreto aggiornato per \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Scansiona il nuovo codice QR per aggiornare il segreto di \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2657,4 +2681,44 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nuovo gruppo';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Tu';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Tracciamento del percorso fallito.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable =>
|
||||
'Tracciamento del percorso non disponibile.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Aggiorna Path Trace.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Traccia Percorso';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Ping';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Traccia percorso al ripetitore';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Ripetitore ping';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace =>
|
||||
'Traccia del percorso al server della stanza';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping al server della stanza';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Traccia percorso path';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Traccia percorso verso $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,6 +444,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Notificaties';
|
||||
|
||||
@@ -1199,6 +1205,24 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
return 'Nieuw: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Link openen?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Wilt u deze link in uw browser openen?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Openen';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Kan link niet openen: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Ongeldig linkformaat';
|
||||
|
||||
@override
|
||||
String get map_title => 'Node Map';
|
||||
|
||||
@@ -2556,32 +2580,32 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Regeneer Geheimwoord';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Regeneere de geheime sleutel voor \"$name\"? Alle leden moeten de nieuwe QR-code scannen om verder te communiceren.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Regeneer';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Geheim hersteld voor \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Bijwerken Geheime';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Geheim gewijzigd voor \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Scan de nieuwe QR-code om het geheim voor \"$name\" bij te werken';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2648,4 +2672,42 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nieuwe groep';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Jij';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Padtrace mislukt.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Padtrace niet beschikbaar.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Path Trace vernieuwen.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Pad Traceren';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Pingen';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Pad traceren naar repeater';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Ping repeater';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Padtrace naar room server';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping kamer server';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Route traceren';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Trace route to $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,6 +448,12 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Powiadomienia';
|
||||
|
||||
@@ -1205,6 +1211,24 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
return 'Niezgłoszone: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Otworzyć link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Czy chcesz otworzyć ten link w przeglądarce?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Otwórz';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Nie można otworzyć linku: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Nieprawidłowy format linku';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa węzłów';
|
||||
|
||||
@@ -2564,32 +2588,32 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Zregeneruj sekret';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Regeneruj tajny klucz dla \"$name\"? Wszyscy członkowie będą musieli zeskanować nowy kod QR, aby kontynuować komunikację.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Zregeneruj';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Hasło ponownie wygenerowane dla \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Zaktualizuj tajny klucz';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Hasło zaktualizowane dla \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Skanuj nowy kod QR, aby zaktualizować sekret dla \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2656,4 +2680,43 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nowa grupa';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Ty';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Śledzenie ścieżki nie powiodło się.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Ścieżka śledzenia niedostępna.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Odśwież ścieżkę.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Śledzenie Ścieżek';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Pingować';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Śledzenie ścieżki do repeatera';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Repeater pingowy';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace =>
|
||||
'Śledzenie ścieżki do serwera pokojowego';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Pinguj serwer pokoju';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Śledź trasę promienia';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Śledź trasę do $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,6 +448,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Notificações';
|
||||
|
||||
@@ -1204,6 +1210,24 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
return 'Não lido: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Abrir link?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Deseja abrir este link no seu navegador?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Abrir';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Não foi possível abrir o link: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Formato de link inválido';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa de Nós';
|
||||
|
||||
@@ -2567,32 +2591,32 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Regenerar Senha Segura';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Regenerar a chave secreta para \"$name\"? Todos os membros precisarão escanear o novo código QR para continuar a comunicação.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Regenerar';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Senha secreta regenerada para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Atualizar Segredo';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Segredo atualizado para \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Scanar o novo código QR para atualizar o segredo para \"$name\"\n\n\n+++++';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2659,4 +2683,42 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Novo grupo';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Você';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Falha no rastreamento de caminho.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Traçado de caminho não disponível.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Atualizar Path Trace.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Traçado de Caminho';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Pingar';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Traçar caminho para repetidor';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Pingar repetidor';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Traçar caminho para o servidor da sala';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Pingar servidor da sala';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Rastrear rota do caminho';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Rastrear rota para $name';
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -444,6 +444,12 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Upozornenia';
|
||||
|
||||
@@ -1200,6 +1206,24 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
return 'Nezriadené: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Otvoriť odkaz?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Chcete otvoriť tento odkaz v prehliadači?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Otvoriť';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Nepodarilo sa otvoriť odkaz: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Neplatný formát odkazu';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa uzlov';
|
||||
|
||||
@@ -2553,32 +2577,32 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Zobraziť nový tajný kód';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Znovu vygenerovať tajný kľúč pre \"$name\"? Všetci členovia budú musieť skanovať nový QR kód, aby mohli nadviazať komunikáciu.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Znovu vygenerovať';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Záznam pre \"$name\" bol regenerovaný tajne';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Aktualizovať tajné heslo';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Zmena tajnej slova pre \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Skáňte nový QR kód na aktualizáciu tajného hesla pre \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2644,4 +2668,42 @@ class AppLocalizationsSk extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nová skupina';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Vy';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Sledovanie cesty zlyhalo.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Path trace nie je k dispozícii.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Obnoviť Path Trace.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Sledovanie lúčov';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Pingovať';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Sledovanie cesty k opakovaču';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Pingovať opakovač';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Sledovanie cesty k serveru miestnosti';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping server miestnosti';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Sledovať trasu lúča';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Sledovať trasu k $name';
|
||||
}
|
||||
}
|
||||
|
||||
+208
-148
@@ -12,7 +12,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get appTitle => 'MeshCore Open';
|
||||
|
||||
@override
|
||||
String get nav_contacts => 'Kontakti';
|
||||
String get nav_contacts => 'Stiki';
|
||||
|
||||
@override
|
||||
String get nav_channels => 'Kanali';
|
||||
@@ -144,7 +144,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get scanner_scan => 'Skeniraj';
|
||||
|
||||
@override
|
||||
String get device_quickSwitch => 'Hitro preklopiti';
|
||||
String get device_quickSwitch => 'Hitro preklop';
|
||||
|
||||
@override
|
||||
String get device_meshcore => 'MeshCore';
|
||||
@@ -163,16 +163,16 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
'Obveščanja, sporoščanje in zemljevidi.';
|
||||
|
||||
@override
|
||||
String get settings_nodeSettings => 'Nastavitve časa';
|
||||
String get settings_nodeSettings => 'Nastavitev časa';
|
||||
|
||||
@override
|
||||
String get settings_nodeName => 'Ime omrežno mesto';
|
||||
String get settings_nodeName => 'Ime node-a';
|
||||
|
||||
@override
|
||||
String get settings_nodeNameNotSet => 'Nezavedeno';
|
||||
String get settings_nodeNameNotSet => 'Ni nastavljeno';
|
||||
|
||||
@override
|
||||
String get settings_nodeNameHint => 'Vnesite ime časa';
|
||||
String get settings_nodeNameHint => 'Vnesite ime node-a';
|
||||
|
||||
@override
|
||||
String get settings_nodeNameUpdated => 'Ime posodobljeno';
|
||||
@@ -182,7 +182,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get settings_radioSettingsSubtitle =>
|
||||
'Frekvenca, moč, razširni faktor';
|
||||
'Frekvenca, moč, razširitveni faktor';
|
||||
|
||||
@override
|
||||
String get settings_radioSettingsUpdated => 'Radio nastavitve posodobljene';
|
||||
@@ -201,7 +201,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get settings_locationInvalid =>
|
||||
'Neveljna zemeljska širina ali dolžina.';
|
||||
'Neveljavna zemeljska širina ali dolžina.';
|
||||
|
||||
@override
|
||||
String get settings_locationGPSEnable => 'Omogoči GPS';
|
||||
@@ -224,7 +224,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get settings_longitude => 'Dolžina';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Mod podjetja';
|
||||
String get settings_privacyMode => 'Zasebnost';
|
||||
|
||||
@override
|
||||
String get settings_privacyModeSubtitle => 'Skrita imena/lokacije v oglasih';
|
||||
@@ -234,10 +234,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
'Omogoči način zasebnosti, da skrijemo tvoje ime in lokacijo v oglasih.';
|
||||
|
||||
@override
|
||||
String get settings_privacyModeEnabled => 'Privatni režim je omogočen.';
|
||||
String get settings_privacyModeEnabled => 'Privatni način je omogočen.';
|
||||
|
||||
@override
|
||||
String get settings_privacyModeDisabled => 'Privatni režim je onemogočen.';
|
||||
String get settings_privacyModeDisabled => 'Privatni način je onemogočen.';
|
||||
|
||||
@override
|
||||
String get settings_actions => 'Akcije';
|
||||
@@ -253,47 +253,46 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get settings_advertisementSent => 'Oglas poslan';
|
||||
|
||||
@override
|
||||
String get settings_syncTime => 'Ugasniti čas';
|
||||
String get settings_syncTime => 'Nastavi uro';
|
||||
|
||||
@override
|
||||
String get settings_syncTimeSubtitle => 'Nastavi uro naprave v čas telefona';
|
||||
String get settings_syncTimeSubtitle => 'Nastavi uro naprave na čas telefona';
|
||||
|
||||
@override
|
||||
String get settings_timeSynchronized => 'Sinhronizirano po času';
|
||||
String get settings_timeSynchronized => 'Ura sinhronizirana';
|
||||
|
||||
@override
|
||||
String get settings_refreshContacts => 'Ponovno obišči kontakte';
|
||||
|
||||
@override
|
||||
String get settings_refreshContactsSubtitle =>
|
||||
'Ponovno naloži seznam kontaktov iz naprave';
|
||||
'Ponovno naloži seznam stikov v napravi';
|
||||
|
||||
@override
|
||||
String get settings_rebootDevice => 'Restart Naprave';
|
||||
String get settings_rebootDevice => 'Ponovni zagon naprave';
|
||||
|
||||
@override
|
||||
String get settings_rebootDeviceSubtitle =>
|
||||
'Ponovite zažetek naprave MeshCore';
|
||||
String get settings_rebootDeviceSubtitle => 'Ponovno zaženi MeshCore napravo';
|
||||
|
||||
@override
|
||||
String get settings_rebootDeviceConfirm =>
|
||||
'Ste prepričani, da želite ponovno zagon napravke? Boste odvisni od omrežja.';
|
||||
'Ste prepričani, da želite ponovno zagnati napravo? Povezava bo prekinjena.';
|
||||
|
||||
@override
|
||||
String get settings_debug => 'Napravi popravek';
|
||||
String get settings_debug => 'Debug';
|
||||
|
||||
@override
|
||||
String get settings_bleDebugLog => 'Logarjev zapis BLE';
|
||||
String get settings_bleDebugLog => 'BLE debug log (razhroščevanje)';
|
||||
|
||||
@override
|
||||
String get settings_bleDebugLogSubtitle =>
|
||||
'Navodila BLE, odgovori in surovo podatkovno';
|
||||
'BLE ukazi, odgovori in surovi podatki';
|
||||
|
||||
@override
|
||||
String get settings_appDebugLog => 'Log zapiske aplikacije';
|
||||
String get settings_appDebugLog => 'Logi aplikacije';
|
||||
|
||||
@override
|
||||
String get settings_appDebugLogSubtitle => 'Prijavni sporočila aplikacije';
|
||||
String get settings_appDebugLogSubtitle => 'Debug sporočila aplikacije';
|
||||
|
||||
@override
|
||||
String get settings_about => 'Oglejte si';
|
||||
@@ -304,11 +303,11 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get settings_aboutLegalese => 'MeshCore Odprtokodni Projekt 2024';
|
||||
String get settings_aboutLegalese => 'Odprtokodni projekt MeshCore 2024';
|
||||
|
||||
@override
|
||||
String get settings_aboutDescription =>
|
||||
'Odprtokodni Flutter kličnik za naprave za LoRa mrežo MeshCore.';
|
||||
'Odprtokodni Flutter klient za naprave za LoRa omrežje MeshCore.';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Ime';
|
||||
@@ -323,10 +322,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get settings_infoBattery => 'Baterija';
|
||||
|
||||
@override
|
||||
String get settings_infoPublicKey => 'Ključ javnega tipa';
|
||||
String get settings_infoPublicKey => 'Javni ključ';
|
||||
|
||||
@override
|
||||
String get settings_infoContactsCount => 'Število kontaktov';
|
||||
String get settings_infoContactsCount => 'Število stikov';
|
||||
|
||||
@override
|
||||
String get settings_infoChannelCount => 'Število kanalov';
|
||||
@@ -350,7 +349,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get settings_frequencyHelper => '300,00 - 2500,00';
|
||||
|
||||
@override
|
||||
String get settings_frequencyInvalid => 'Neveljčna frekvenca (300-2500 MHz)';
|
||||
String get settings_frequencyInvalid => 'Neveljavna frekvenca (300-2500 MHz)';
|
||||
|
||||
@override
|
||||
String get settings_bandwidth => 'Pasovna širina';
|
||||
@@ -368,13 +367,13 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get settings_txPowerHelper => '0 - 22';
|
||||
|
||||
@override
|
||||
String get settings_txPowerInvalid => 'Neveljaven TX moč (0-22 dBm)';
|
||||
String get settings_txPowerInvalid => 'Neveljavna TX moč (0-22 dBm)';
|
||||
|
||||
@override
|
||||
String get settings_longRange => 'Dolenje območje';
|
||||
String get settings_longRange => 'DDolg doseg';
|
||||
|
||||
@override
|
||||
String get settings_fastSpeed => 'Hitra hitrost';
|
||||
String get settings_fastSpeed => 'Visoka hitrost';
|
||||
|
||||
@override
|
||||
String settings_error(String message) {
|
||||
@@ -391,10 +390,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get appSettings_theme => 'Tema';
|
||||
|
||||
@override
|
||||
String get appSettings_themeSystem => 'Predpomnilnik sistema';
|
||||
String get appSettings_themeSystem => 'Sistemska tema';
|
||||
|
||||
@override
|
||||
String get appSettings_themeLight => 'Luč';
|
||||
String get appSettings_themeLight => 'Svetlo';
|
||||
|
||||
@override
|
||||
String get appSettings_themeDark => 'Temno';
|
||||
@@ -445,10 +444,16 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Obveščanja';
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_enableNotifications => 'Omogoči obveščanje';
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Obvestila';
|
||||
|
||||
@override
|
||||
String get appSettings_enableNotifications => 'Omogoči obvestila';
|
||||
|
||||
@override
|
||||
String get appSettings_enableNotificationsSubtitle =>
|
||||
@@ -484,7 +489,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_advertisementNotificationsSubtitle =>
|
||||
'Pokaži obvestilo, ko so novi vozlišči odkrivljeni.';
|
||||
'Pokaži obvestilo, ko so najdene nove naprave.';
|
||||
|
||||
@override
|
||||
String get appSettings_messaging => 'Komuniciranje';
|
||||
@@ -499,18 +504,19 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get appSettings_pathsWillBeCleared =>
|
||||
'Potnice bodo očiščene po 5 neuspešnih poskusih.';
|
||||
'Počisti pot po 5 neuspešnih poskusih.';
|
||||
|
||||
@override
|
||||
String get appSettings_pathsWillNotBeCleared =>
|
||||
'Potniški poti ne bodo samodejno čiščeni.';
|
||||
'Poti ne bodo samodejno čiščene.';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotation => 'Avtomatsko Občutke in Rotacije';
|
||||
String get appSettings_autoRouteRotation =>
|
||||
'Avtomatsko rotacija prenosne poti';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotationSubtitle =>
|
||||
'Med spreminjanjem med najboljšimi potmi in plovilnim načinom';
|
||||
'Menjaj med boljšo potjo in flood načinom';
|
||||
|
||||
@override
|
||||
String get appSettings_autoRouteRotationEnabled =>
|
||||
@@ -524,16 +530,16 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get appSettings_battery => 'Baterija';
|
||||
|
||||
@override
|
||||
String get appSettings_batteryChemistry => 'Razem z možnostmi';
|
||||
String get appSettings_batteryChemistry => 'Kemija baterije';
|
||||
|
||||
@override
|
||||
String appSettings_batteryChemistryPerDevice(String deviceName) {
|
||||
return 'Nastavitve za naprave ($deviceName)';
|
||||
return 'Nastavitev za napravo ($deviceName)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_batteryChemistryConnectFirst =>
|
||||
'Povežite se z napravo za izbiro';
|
||||
'Za izbiro se poveži z napravo';
|
||||
|
||||
@override
|
||||
String get appSettings_batteryNmc => '18650 NMC (3,0-4,2V)';
|
||||
@@ -545,52 +551,51 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get appSettings_batteryLipo => 'LiPo (3,0-4,2V)';
|
||||
|
||||
@override
|
||||
String get appSettings_mapDisplay => 'Prikaz zemljevide';
|
||||
String get appSettings_mapDisplay => 'Prikaz zemljevida';
|
||||
|
||||
@override
|
||||
String get appSettings_showRepeaters => 'Prikaži ponovitve';
|
||||
String get appSettings_showRepeaters => 'Prikaži repetitorje';
|
||||
|
||||
@override
|
||||
String get appSettings_showRepeatersSubtitle =>
|
||||
'Prikaži ponovljalne notranjosti na zemljeploscu';
|
||||
String get appSettings_showRepeatersSubtitle => 'Prikaži repetitorje na mapi';
|
||||
|
||||
@override
|
||||
String get appSettings_showChatNodes => 'Prikaži čakalne notranjosti';
|
||||
String get appSettings_showChatNodes => 'Prikaži naprave za klepet';
|
||||
|
||||
@override
|
||||
String get appSettings_showChatNodesSubtitle =>
|
||||
'Prikaži pogovorni pike na zemljeploscu';
|
||||
'Prikaži naprave na zemljevidu';
|
||||
|
||||
@override
|
||||
String get appSettings_showOtherNodes => 'Pokaži druge vozlišča';
|
||||
String get appSettings_showOtherNodes => 'Pokaži druge naprave';
|
||||
|
||||
@override
|
||||
String get appSettings_showOtherNodesSubtitle =>
|
||||
'Pokaži druge vrste notranjih elementov na zemljevalu.';
|
||||
'Pokaži druge vrste naprav na zemljevidu.';
|
||||
|
||||
@override
|
||||
String get appSettings_timeFilter => 'Filtri po času';
|
||||
String get appSettings_timeFilter => 'Filter po času';
|
||||
|
||||
@override
|
||||
String get appSettings_timeFilterShowAll => 'Pokaži vse notranje elemente';
|
||||
String get appSettings_timeFilterShowAll => 'Pokaži vse naprave';
|
||||
|
||||
@override
|
||||
String appSettings_timeFilterShowLast(int hours) {
|
||||
return 'Pokaži notranjosti iz zadnjih $hours ur';
|
||||
return 'Pokaži naprave v zadnjih $hours urah';
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_mapTimeFilter => 'Filtri časa zemljevida';
|
||||
String get appSettings_mapTimeFilter => 'Filter časa na zemljevidu';
|
||||
|
||||
@override
|
||||
String get appSettings_showNodesDiscoveredWithin =>
|
||||
'Pokaži notranje čepke, odkrivene v:';
|
||||
'Pokaži naprave odkrite v:';
|
||||
|
||||
@override
|
||||
String get appSettings_allTime => 'Vse čase';
|
||||
String get appSettings_allTime => 'Brez omejitev';
|
||||
|
||||
@override
|
||||
String get appSettings_lastHour => 'Minuto nazaj';
|
||||
String get appSettings_lastHour => 'V zadnji uri';
|
||||
|
||||
@override
|
||||
String get appSettings_last6Hours => 'Zadnjih 6 ur';
|
||||
@@ -599,13 +604,13 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get appSettings_last24Hours => 'Zadnjih 24 ur';
|
||||
|
||||
@override
|
||||
String get appSettings_lastWeek => 'Lepošno';
|
||||
String get appSettings_lastWeek => 'Prejšnji teden';
|
||||
|
||||
@override
|
||||
String get appSettings_offlineMapCache => 'Omrezni Poudni Arhiv';
|
||||
String get appSettings_offlineMapCache => 'Shramba zemljevidov brez povezave';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Nizkana označena površina';
|
||||
String get appSettings_noAreaSelected => 'Območje ni izbrano';
|
||||
|
||||
@override
|
||||
String appSettings_areaSelectedZoom(int minZoom, int maxZoom) {
|
||||
@@ -613,79 +618,78 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get appSettings_debugCard => 'Napravi popravek';
|
||||
String get appSettings_debugCard => 'Razhroščevanje';
|
||||
|
||||
@override
|
||||
String get appSettings_appDebugLogging => 'Programski Log';
|
||||
String get appSettings_appDebugLogging => 'Programski dnevnik';
|
||||
|
||||
@override
|
||||
String get appSettings_appDebugLoggingSubtitle =>
|
||||
'Log aplikacijske debug sporočila za odpravljanje težav';
|
||||
'Dnevnik debug sporočil za odpravljanje težav';
|
||||
|
||||
@override
|
||||
String get appSettings_appDebugLoggingEnabled =>
|
||||
'Omogočeno zaznamovanje napak v aplikaciji';
|
||||
'Beleženje napak v aplikaciji omogočeno';
|
||||
|
||||
@override
|
||||
String get appSettings_appDebugLoggingDisabled =>
|
||||
'Programski logi aplikacije so onemogočeni.';
|
||||
'Beleženje napak v aplikacije onemogočeno.';
|
||||
|
||||
@override
|
||||
String get contacts_title => 'Kontakti';
|
||||
String get contacts_title => 'Stiki';
|
||||
|
||||
@override
|
||||
String get contacts_noContacts => 'Še ni kontaktov.';
|
||||
String get contacts_noContacts => 'Ni stikov.';
|
||||
|
||||
@override
|
||||
String get contacts_contactsWillAppear =>
|
||||
'Kontakti se bodo prikazali, ko naprave oglasijo.';
|
||||
'Stiki se bodo prikazali, ko se naprave oglasijo.';
|
||||
|
||||
@override
|
||||
String get contacts_searchContacts => 'Iskanje kontaktov...';
|
||||
String get contacts_searchContacts => 'Iskanje stikov...';
|
||||
|
||||
@override
|
||||
String get contacts_noUnreadContacts => 'Nerešeno kontaktov.';
|
||||
String get contacts_noUnreadContacts => 'Ne prebrani stiki.';
|
||||
|
||||
@override
|
||||
String get contacts_noContactsFound =>
|
||||
'Niti ena oseba ali skupine ni najdena.';
|
||||
String get contacts_noContactsFound => 'Stiki niso najdeni.';
|
||||
|
||||
@override
|
||||
String get contacts_deleteContact => 'Izbrisati Kontakt';
|
||||
String get contacts_deleteContact => 'Izbriši stik';
|
||||
|
||||
@override
|
||||
String contacts_removeConfirm(String contactName) {
|
||||
return 'Izbrisati $contactName iz kontaktov?';
|
||||
return 'Izbrišem $contactName iz stikov?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_manageRepeater => 'Upravljajte Ponovitve';
|
||||
String get contacts_manageRepeater => 'Upravljaj Ponovitve';
|
||||
|
||||
@override
|
||||
String get contacts_manageRoom => 'Upravljajte strežnik sobe';
|
||||
|
||||
@override
|
||||
String get contacts_roomLogin => 'Vnos v sobo';
|
||||
String get contacts_roomLogin => 'Prijava v sobo';
|
||||
|
||||
@override
|
||||
String get contacts_openChat => 'Odprta kleta';
|
||||
String get contacts_openChat => 'Odpri klepet';
|
||||
|
||||
@override
|
||||
String get contacts_editGroup => 'Uredi Skupino';
|
||||
String get contacts_editGroup => 'Uredi skupino';
|
||||
|
||||
@override
|
||||
String get contacts_deleteGroup => 'Izbrisati Skupino';
|
||||
String get contacts_deleteGroup => 'Izbriši skupino';
|
||||
|
||||
@override
|
||||
String contacts_deleteGroupConfirm(String groupName) {
|
||||
return 'Odpovedati $groupName?';
|
||||
return 'Izbriši $groupName?';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_newGroup => 'Novo skupino';
|
||||
String get contacts_newGroup => 'Nova skupina';
|
||||
|
||||
@override
|
||||
String get contacts_groupName => 'Skupina imena';
|
||||
String get contacts_groupName => 'Ime skupine';
|
||||
|
||||
@override
|
||||
String get contacts_groupNameRequired => 'Ime skupine je obvezno.';
|
||||
@@ -696,53 +700,53 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_filterContacts => 'Filtri kontakt\\,...';
|
||||
String get contacts_filterContacts => 'Filtriraj stik\\,...';
|
||||
|
||||
@override
|
||||
String get contacts_noContactsMatchFilter =>
|
||||
'Niti ena oseba ne ustreza vašemu kriteriju.';
|
||||
'Noben stik ne ustreza vašemu kriteriju.';
|
||||
|
||||
@override
|
||||
String get contacts_noMembers => 'Nič članov.';
|
||||
String get contacts_noMembers => 'Ni članov.';
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenNow => 'Datum zadnjega vpisa zdaj';
|
||||
String get contacts_lastSeenNow => 'Nazadnje viden zdaj';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenMinsAgo(int minutes) {
|
||||
return 'Zadnjič videti $minutes minut nazaj';
|
||||
return 'Zadnjič viden pred $minutes minutami';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenHourAgo => 'Zadnjič ogledan pred 1 uro.';
|
||||
String get contacts_lastSeenHourAgo => 'Zadnjič viden pred 1 uro.';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenHoursAgo(int hours) {
|
||||
return 'Zadnjič videti $hours ur nazaj';
|
||||
return 'Zadnjič viden pred $hours urami';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contacts_lastSeenDayAgo => 'Zadnjič ogledan pred 1 dnem';
|
||||
String get contacts_lastSeenDayAgo => 'Zadnjič viden pred 1 dnem';
|
||||
|
||||
@override
|
||||
String contacts_lastSeenDaysAgo(int days) {
|
||||
return 'Zadnjič videti $days dni nazaj';
|
||||
return 'Zadnjič viden pred $days dnem';
|
||||
}
|
||||
|
||||
@override
|
||||
String get channels_title => 'Kanali';
|
||||
|
||||
@override
|
||||
String get channels_noChannelsConfigured => 'Nekonfigurirane kanale';
|
||||
String get channels_noChannelsConfigured => 'Kanali še niso konfigurirani';
|
||||
|
||||
@override
|
||||
String get channels_addPublicChannel => 'Dodaj Objavni Kanal';
|
||||
String get channels_addPublicChannel => 'Dodaj javni kanal';
|
||||
|
||||
@override
|
||||
String get channels_searchChannels => 'Poišči kanale...';
|
||||
|
||||
@override
|
||||
String get channels_noChannelsFound => 'Niti kanalov najti ni.';
|
||||
String get channels_noChannelsFound => 'Ne najdem kanalov.';
|
||||
|
||||
@override
|
||||
String channels_channelIndex(int index) {
|
||||
@@ -753,16 +757,16 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get channels_hashtagChannel => 'Hashtag kanal';
|
||||
|
||||
@override
|
||||
String get channels_public => 'javno';
|
||||
String get channels_public => 'Javni';
|
||||
|
||||
@override
|
||||
String get channels_private => 'Zasebno';
|
||||
String get channels_private => 'Zasebni';
|
||||
|
||||
@override
|
||||
String get channels_publicChannel => 'Ogljišna skupina';
|
||||
String get channels_publicChannel => 'Javni kanal';
|
||||
|
||||
@override
|
||||
String get channels_privateChannel => 'Zatemniščen kanal';
|
||||
String get channels_privateChannel => 'Zasebni kanal';
|
||||
|
||||
@override
|
||||
String get channels_editChannel => 'Uredi kanal';
|
||||
@@ -772,7 +776,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String channels_deleteChannelConfirm(String name) {
|
||||
return 'Izbrisati \"$name\"? To se ne da povrniti.';
|
||||
return 'Izbrišem \"$name\"? To se ne da povrniti.';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -912,21 +916,21 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_typeMessage => 'Vnesite sporočilo...';
|
||||
String get chat_typeMessage => 'Vnesi sporočilo...';
|
||||
|
||||
@override
|
||||
String chat_messageTooLong(int maxBytes) {
|
||||
return 'Pošiljanje sporočila je onemogočeno, saj je preveliko (maksimalno $maxBytes bajt).';
|
||||
return 'Pošiljanje sporočila je onemogočeno, saj je preveliko (maksimalno $maxBytes byte-ov).';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_messageCopied => 'Pošljeno sporočilo';
|
||||
String get chat_messageCopied => 'Sporočilo poslano';
|
||||
|
||||
@override
|
||||
String get chat_messageDeleted => 'Pošiljanje sporočila izbrisano';
|
||||
String get chat_messageDeleted => 'Sporočilo izbrisano';
|
||||
|
||||
@override
|
||||
String get chat_retryingMessage => 'Ponovna poskus.';
|
||||
String get chat_retryingMessage => 'Ponovni poskus.';
|
||||
|
||||
@override
|
||||
String chat_retryCount(int current, int max) {
|
||||
@@ -937,10 +941,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get chat_sendGif => 'Pošlji GIF';
|
||||
|
||||
@override
|
||||
String get chat_reply => 'Odpošlji';
|
||||
String get chat_reply => 'Odgovori';
|
||||
|
||||
@override
|
||||
String get chat_addReaction => 'Dodaj Reakcijo';
|
||||
String get chat_addReaction => 'Dodaj reakcijo';
|
||||
|
||||
@override
|
||||
String get chat_me => 'jaz';
|
||||
@@ -961,19 +965,19 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get gifPicker_title => 'Izberi GIF';
|
||||
|
||||
@override
|
||||
String get gifPicker_searchHint => 'Iskalite GIF-e...';
|
||||
String get gifPicker_searchHint => 'Išči GIF-e...';
|
||||
|
||||
@override
|
||||
String get gifPicker_poweredBy => 'Naprodno z GIPHY';
|
||||
String get gifPicker_poweredBy => 'Napredno z GIPHY';
|
||||
|
||||
@override
|
||||
String get gifPicker_noGifsFound => 'Niti GIF-jev najti ni.';
|
||||
String get gifPicker_noGifsFound => 'Ne najdem GIF-ov.';
|
||||
|
||||
@override
|
||||
String get gifPicker_failedLoad => 'Neuspešno je naložilo GIF-e';
|
||||
String get gifPicker_failedLoad => 'Neuspešno nalaganje GIF-a';
|
||||
|
||||
@override
|
||||
String get gifPicker_failedSearch => 'Posodobit neuspešno.';
|
||||
String get gifPicker_failedSearch => 'Iskanje neuspešno.';
|
||||
|
||||
@override
|
||||
String get gifPicker_noInternet => 'Ni internetne povezave';
|
||||
@@ -982,35 +986,35 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get debugLog_appTitle => 'Log zapiske aplikacije';
|
||||
|
||||
@override
|
||||
String get debugLog_bleTitle => 'Logarjev zapis BLE';
|
||||
String get debugLog_bleTitle => 'Log zapis BLE';
|
||||
|
||||
@override
|
||||
String get debugLog_copyLog => 'Kopiraj zapiske';
|
||||
String get debugLog_copyLog => 'Kopiraj dnevnik';
|
||||
|
||||
@override
|
||||
String get debugLog_clearLog => 'Pasters log';
|
||||
String get debugLog_clearLog => 'Briši log';
|
||||
|
||||
@override
|
||||
String get debugLog_copied => 'Kopirana belež poteka.';
|
||||
String get debugLog_copied => 'Beležka kopirana.';
|
||||
|
||||
@override
|
||||
String get debugLog_bleCopied => 'Kopirana beležke iz BLE';
|
||||
String get debugLog_bleCopied => 'Kopirana beležka iz BLE';
|
||||
|
||||
@override
|
||||
String get debugLog_noEntries => 'Še ni ustvarjenih debug zapisov.';
|
||||
String get debugLog_noEntries => 'Ni ustvarjenih debug zapisov.';
|
||||
|
||||
@override
|
||||
String get debugLog_enableInSettings =>
|
||||
'Omogoči beleženje napak v aplikaciji v nastavitvah';
|
||||
'Omogoči beleženje napak v nastavitvah aplikacije';
|
||||
|
||||
@override
|
||||
String get debugLog_frames => 'Okna';
|
||||
String get debugLog_frames => 'Okvirji';
|
||||
|
||||
@override
|
||||
String get debugLog_rawLogRx => 'Svež Log-RX';
|
||||
|
||||
@override
|
||||
String get debugLog_noBleActivity => 'Šele začnite z aktivnostjo BLE.';
|
||||
String get debugLog_noBleActivity => 'Ni BLE aktivnosti.';
|
||||
|
||||
@override
|
||||
String debugFrame_length(int count) {
|
||||
@@ -1079,10 +1083,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
'Zapiske o poti so popolni. Izbriši vnose, da dodaš nove.';
|
||||
|
||||
@override
|
||||
String get chat_hopSingular => 'skoč';
|
||||
String get chat_hopSingular => 'skok';
|
||||
|
||||
@override
|
||||
String get chat_hopPlural => 'škrabec';
|
||||
String get chat_hopPlural => 'skokov';
|
||||
|
||||
@override
|
||||
String chat_hopsCount(int count) {
|
||||
@@ -1103,7 +1107,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get chat_noPathHistoryYet =>
|
||||
'Še ni shranjenih poti.\nPošlji sporočilo za odkrivanje poti.';
|
||||
'Ni shranjenih poti.\nPošlji sporočilo za odkrivanje poti.';
|
||||
|
||||
@override
|
||||
String get chat_pathActions => 'Potni ukazi:';
|
||||
@@ -1115,7 +1119,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get chat_setCustomPathSubtitle => 'Ročno določite potniško pot.';
|
||||
|
||||
@override
|
||||
String get chat_clearPath => 'Čista pot';
|
||||
String get chat_clearPath => 'Počisti pot';
|
||||
|
||||
@override
|
||||
String get chat_clearPathSubtitle => 'Ob naslednji pošiljanju znova zbrati.';
|
||||
@@ -1133,7 +1137,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
'Narejena je bila omrežna modaliteta. Vklopi jo znova preko ikone v meniju aplikacije.';
|
||||
|
||||
@override
|
||||
String get chat_fullPath => 'Polni pot';
|
||||
String get chat_fullPath => 'Polna pot';
|
||||
|
||||
@override
|
||||
String get chat_pathDetailsNotAvailable =>
|
||||
@@ -1197,6 +1201,24 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
return 'Nerešeno: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Odpreti povezavo?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Ali želite odpreti to povezavo v brskalniku?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Odpri';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Povezave ni bilo mogoče odpreti: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Neveljavna oblika povezave';
|
||||
|
||||
@override
|
||||
String get map_title => 'Mapa omrežja';
|
||||
|
||||
@@ -1994,13 +2016,13 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickGetName => 'Dobiti ime';
|
||||
String get repeater_cliQuickGetName => 'Pridobi ime';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickGetRadio => 'Dobiti Radiopravo';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickGetTx => 'Dobiti TX';
|
||||
String get repeater_cliQuickGetTx => 'Pridobi TX';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickNeighbors => 'Sosedi';
|
||||
@@ -2012,7 +2034,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get repeater_cliQuickAdvertise => 'Oglasite';
|
||||
|
||||
@override
|
||||
String get repeater_cliQuickClock => 'Urnik';
|
||||
String get repeater_cliQuickClock => 'Ura';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpAdvert => 'Pošlje paket oglasov';
|
||||
@@ -2135,7 +2157,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpSetPerm =>
|
||||
'Modificira ACL. Odstrani ustreznu vnos (po predponi pubkeyja), če je \"permissions\" enako nič. Dodaja nov vnos, če je pubkey-hex v celoti in trenutno ni v ACL. Posodobi vnos po ustreznem predponi pubkeyja. Bitje dovoljenj se razlikuje glede na firmware vlogo, vendar so prvi dve bitki: 0 (Gost), 1 (Lezenje samo), 2 (Lezenje in pisanje), 3 (Administrator).';
|
||||
'Modificira ACL. Odstrani ustrezen vnos (po predponi pubkeyja), če je \"permissions\" enako nič. Dodaja nov vnos, če je pubkey-hex v celoti in trenutno ni v ACL. Posodobi vnos po ustreznem predponi pubkeyja. Bitje dovoljenj se razlikuje glede na firmware vlogo, vendar so prvi dve bitki: 0 (Gost), 1 (Lezenje samo), 2 (Lezenje in pisanje), 3 (Administrator).';
|
||||
|
||||
@override
|
||||
String get repeater_cliHelpGetBridgeType =>
|
||||
@@ -2243,11 +2265,11 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get repeater_logging => 'Logiranje';
|
||||
|
||||
@override
|
||||
String get repeater_neighborsRepeaterOnly => 'Sosedi (le za ponovitelja)';
|
||||
String get repeater_neighborsRepeaterOnly => 'Sosedi (le za repetitorje)';
|
||||
|
||||
@override
|
||||
String get repeater_regionManagementRepeaterOnly =>
|
||||
'Upravljanje regij (zgolj za ponovitve)';
|
||||
'Upravljanje regij (zgolj za repetitorje)';
|
||||
|
||||
@override
|
||||
String get repeater_regionNote =>
|
||||
@@ -2362,13 +2384,13 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
String get channelPath_messageDetails => 'Podrobnosti sporočila';
|
||||
|
||||
@override
|
||||
String get channelPath_senderLabel => 'Pošiljalec';
|
||||
String get channelPath_senderLabel => 'Pošiljatelj';
|
||||
|
||||
@override
|
||||
String get channelPath_timeLabel => 'Čas';
|
||||
String get channelPath_timeLabel => 'Ura';
|
||||
|
||||
@override
|
||||
String get channelPath_repeatsLabel => 'Ponovi';
|
||||
String get channelPath_repeatsLabel => 'Ponovitve';
|
||||
|
||||
@override
|
||||
String channelPath_pathLabel(int index) {
|
||||
@@ -2533,17 +2555,17 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get community_scanOrCreate =>
|
||||
'Skenirajte QR kodo ali ustvarite skupnost za začetek.';
|
||||
'Skeniraj QR kodo ali ustvari skupnost za začetek.';
|
||||
|
||||
@override
|
||||
String get community_manageCommunities => 'Upravljajte skupnosti';
|
||||
String get community_manageCommunities => 'Upravljanje skupnosti';
|
||||
|
||||
@override
|
||||
String get community_delete => 'Opusti skupnost';
|
||||
|
||||
@override
|
||||
String community_deleteConfirm(String name) {
|
||||
return 'Zapustiti \"$name\"?';
|
||||
return 'Zapusti \"$name\"?';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2557,36 +2579,36 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Ponovno ustvari geslo';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Preberite novo tajno geslo za \"$name\"? Vsi članici morajo prebrati novo QR kodo, da lahko nadaljujejo s komunikacijo.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Preberi znova';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Geslo za \"$name\" ponovno ustvarjeno';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Ažuriraj ključ';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Skrivnostno spremembo za \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Skeniraj novo QR kodo za posodabljanje ključa za $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannel => 'Dodaj Oznako Obštnine';
|
||||
String get community_addHashtagChannel => 'Dodaj hashtag kanal';
|
||||
|
||||
@override
|
||||
String get community_addHashtagChannelDesc =>
|
||||
@@ -2600,7 +2622,7 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get community_regularHashtagDesc =>
|
||||
'javna oznaka (kateri koli lahko sodelujejo)';
|
||||
'javna oznaka (kdorkoli lahko sodeluje)';
|
||||
|
||||
@override
|
||||
String get community_communityHashtag => 'Skupnostni hashtag';
|
||||
@@ -2649,4 +2671,42 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Nova skupina';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Ti';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Sledenje poti ni uspelo.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Potni sled ni na voljo.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Osveži Path Trace.';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Sledenje poti';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Pingati';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Sledi poti do ponavljalnika';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Pinguj ponavljalnik';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Sledenje poti do strežnika sobe';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping strežnik sobe';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Slediti poti žarkov';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Trace route to $name';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,6 +441,12 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => 'Meddelanden';
|
||||
|
||||
@@ -1192,6 +1198,24 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
return 'Olästa: $count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => 'Öppna länk?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation =>
|
||||
'Vill du öppna den här länken i din webbläsare?';
|
||||
|
||||
@override
|
||||
String get chat_open => 'Öppna';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return 'Kunde inte öppna länken: $url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => 'Ogiltigt länkformat';
|
||||
|
||||
@override
|
||||
String get map_title => 'Nodkarta';
|
||||
|
||||
@@ -2541,32 +2565,32 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => 'Regenerera hemlig kod';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return 'Regenerera den hemliga nyckeln för \"$name\"? Alla medlemmar måste scanna den nya QR-koden för att fortsätta kommunicera.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => 'Regenerera';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return 'Lösenord återskapad för \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => 'Uppdatera hemlighet';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return 'Hemlighet uppdaterad för \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return 'Skanna den nya QR-koden för att uppdatera hemligheten för \"$name\"';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2632,4 +2656,42 @@ class AppLocalizationsSv extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => 'Ny grupp';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => 'Du';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => 'Sökvägsföljning misslyckades.';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => 'Path trace ej tillgänglig.';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => 'Uppdatera Path Trace';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Path Trace';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'Ping';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => 'Vägspårning till repeater';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Ping-repeater';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => 'Vägspårning till rumserver';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping rumsserver';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => 'Spåra rutt';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return 'Spåra rutt till $name';
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -432,6 +432,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get appSettings_languageBg => 'Български';
|
||||
|
||||
@override
|
||||
String get appSettings_languageRu => 'Русский';
|
||||
|
||||
@override
|
||||
String get appSettings_languageUk => 'Українська';
|
||||
|
||||
@override
|
||||
String get appSettings_notifications => '通知';
|
||||
|
||||
@@ -1148,6 +1154,23 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
return '未读:$count';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_openLink => '打开链接?';
|
||||
|
||||
@override
|
||||
String get chat_openLinkConfirmation => '您想在浏览器中打开此链接吗?';
|
||||
|
||||
@override
|
||||
String get chat_open => '打开';
|
||||
|
||||
@override
|
||||
String chat_couldNotOpenLink(String url) {
|
||||
return '无法打开链接:$url';
|
||||
}
|
||||
|
||||
@override
|
||||
String get chat_invalidLink => '链接格式无效';
|
||||
|
||||
@override
|
||||
String get map_title => '节点地图';
|
||||
|
||||
@@ -2425,32 +2448,32 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerateSecret => 'Regenerate Secret';
|
||||
String get community_regenerateSecret => '重新生成密钥';
|
||||
|
||||
@override
|
||||
String community_regenerateSecretConfirm(String name) {
|
||||
return 'Regenerate the secret key for \"$name\"? All members will need to scan the new QR code to continue communicating.';
|
||||
return '重新生成“$name”的秘密密钥?所有成员将需要扫描新的二维码才能继续沟通。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_regenerate => 'Regenerate';
|
||||
String get community_regenerate => '重新生成';
|
||||
|
||||
@override
|
||||
String community_secretRegenerated(String name) {
|
||||
return 'Secret regenerated for \"$name\"';
|
||||
return '密码已重置为“$name”';
|
||||
}
|
||||
|
||||
@override
|
||||
String get community_updateSecret => 'Update Secret';
|
||||
String get community_updateSecret => '更新密钥';
|
||||
|
||||
@override
|
||||
String community_secretUpdated(String name) {
|
||||
return 'Secret updated for \"$name\"';
|
||||
return '密码已更新为“$name”';
|
||||
}
|
||||
|
||||
@override
|
||||
String community_scanToUpdateSecret(String name) {
|
||||
return 'Scan the new QR code to update the secret for \"$name\"';
|
||||
return '扫描新的二维码更新\"$name\"的密码';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2514,4 +2537,42 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get listFilter_newGroup => '新组';
|
||||
|
||||
@override
|
||||
String get pathTrace_you => '你';
|
||||
|
||||
@override
|
||||
String get pathTrace_failed => '路径追踪失败。';
|
||||
|
||||
@override
|
||||
String get pathTrace_notAvailable => '路径追踪不可用';
|
||||
|
||||
@override
|
||||
String get pathTrace_refreshTooltip => '刷新路径追踪';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => '路径追踪';
|
||||
|
||||
@override
|
||||
String get contacts_ping => 'ping';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPathTrace => '路径追踪到中继器';
|
||||
|
||||
@override
|
||||
String get contacts_repeaterPing => 'Ping 中继器';
|
||||
|
||||
@override
|
||||
String get contacts_roomPathTrace => '路径追踪至房间服务器';
|
||||
|
||||
@override
|
||||
String get contacts_roomPing => 'Ping 房间服务器';
|
||||
|
||||
@override
|
||||
String get contacts_chatTraceRoute => '路径追踪';
|
||||
|
||||
@override
|
||||
String contacts_pathTraceTo(String name) {
|
||||
return '追踪路由到 $name';
|
||||
}
|
||||
}
|
||||
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Link openen?",
|
||||
"chat_openLinkConfirmation": "Wilt u deze link in uw browser openen?",
|
||||
"chat_open": "Openen",
|
||||
"chat_couldNotOpenLink": "Kan link niet openen: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Ongeldig linkformaat",
|
||||
"map_title": "Node Map",
|
||||
"map_noNodesWithLocation": "Geen nodes met locatiegegevens",
|
||||
"map_nodesNeedGps": "Nodes moeten hun GPS-coördinaten delen\nom op de kaart te verschijnen",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Dit verwijdert ook {count} kanaal/kanalen en hun berichten.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Community \"{name}\" verlaten",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Open hashtag (iedereen kan deelnemen)",
|
||||
"community_communityHashtag": "Gemeenschappelijk Hashtag",
|
||||
"community_communityHashtagDesc": "Alleen zichtbaar voor leden van de community",
|
||||
"community_forCommunity": "Voor {name}"
|
||||
"community_forCommunity": "Voor {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_secretRegenerated": "Geheim hersteld voor \"{name}\"",
|
||||
"community_regenerateSecret": "Regeneer Geheimwoord",
|
||||
"community_regenerateSecretConfirm": "Regeneere de geheime sleutel voor \"{name}\"? Alle leden moeten de nieuwe QR-code scannen om verder te communiceren.",
|
||||
"community_regenerate": "Regeneer",
|
||||
"community_updateSecret": "Bijwerken Geheime",
|
||||
"community_secretUpdated": "Geheim gewijzigd voor \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Scan de nieuwe QR-code om het geheim voor \"{name}\" bij te werken",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Jij",
|
||||
"pathTrace_failed": "Padtrace mislukt.",
|
||||
"pathTrace_notAvailable": "Padtrace niet beschikbaar.",
|
||||
"pathTrace_refreshTooltip": "Path Trace vernieuwen.",
|
||||
"contacts_pathTrace": "Pad Traceren",
|
||||
"contacts_ping": "Pingen",
|
||||
"contacts_repeaterPathTrace": "Pad traceren naar repeater",
|
||||
"contacts_repeaterPing": "Ping repeater",
|
||||
"contacts_roomPathTrace": "Padtrace naar room server",
|
||||
"contacts_roomPing": "Ping kamer server",
|
||||
"contacts_chatTraceRoute": "Route traceren",
|
||||
"contacts_pathTraceTo": "Trace route to {name}"
|
||||
}
|
||||
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Otworzyć link?",
|
||||
"chat_openLinkConfirmation": "Czy chcesz otworzyć ten link w przeglądarce?",
|
||||
"chat_open": "Otwórz",
|
||||
"chat_couldNotOpenLink": "Nie można otworzyć linku: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Nieprawidłowy format linku",
|
||||
"map_title": "Mapa węzłów",
|
||||
"map_noNodesWithLocation": "Brak węzłów z danymi lokalizacyjnymi",
|
||||
"map_nodesNeedGps": "Węzły muszą udostępniać swoje współrzędne GPS,\naby pojawić się na mapie.",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Spowoduje to również usunięcie {count} kanału/kanałów i ich wiadomości.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Opuszczono społeczność \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Publiczny hashtag (każdy może dołączyć)",
|
||||
"community_communityHashtag": "Hashtag Społeczności",
|
||||
"community_communityHashtagDesc": "Dostępne tylko dla członków społeczności",
|
||||
"community_forCommunity": "Dla {name}"
|
||||
"community_forCommunity": "Dla {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerate": "Zregeneruj",
|
||||
"community_secretRegenerated": "Hasło ponownie wygenerowane dla \"{name}\"",
|
||||
"community_regenerateSecret": "Zregeneruj sekret",
|
||||
"community_regenerateSecretConfirm": "Regeneruj tajny klucz dla \"{name}\"? Wszyscy członkowie będą musieli zeskanować nowy kod QR, aby kontynuować komunikację.",
|
||||
"community_scanToUpdateSecret": "Skanuj nowy kod QR, aby zaktualizować sekret dla \"{name}\"",
|
||||
"community_secretUpdated": "Hasło zaktualizowane dla \"{name}\"",
|
||||
"community_updateSecret": "Zaktualizuj tajny klucz",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Ty",
|
||||
"pathTrace_failed": "Śledzenie ścieżki nie powiodło się.",
|
||||
"pathTrace_notAvailable": "Ścieżka śledzenia niedostępna.",
|
||||
"contacts_pathTrace": "Śledzenie Ścieżek",
|
||||
"contacts_ping": "Pingować",
|
||||
"contacts_repeaterPathTrace": "Śledzenie ścieżki do repeatera",
|
||||
"contacts_roomPathTrace": "Śledzenie ścieżki do serwera pokojowego",
|
||||
"contacts_roomPing": "Pinguj serwer pokoju",
|
||||
"pathTrace_refreshTooltip": "Odśwież ścieżkę.",
|
||||
"contacts_repeaterPing": "Repeater pingowy",
|
||||
"contacts_pathTraceTo": "Śledź trasę do {name}",
|
||||
"contacts_chatTraceRoute": "Śledź trasę promienia"
|
||||
}
|
||||
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Abrir link?",
|
||||
"chat_openLinkConfirmation": "Deseja abrir este link no seu navegador?",
|
||||
"chat_open": "Abrir",
|
||||
"chat_couldNotOpenLink": "Não foi possível abrir o link: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Formato de link inválido",
|
||||
"map_title": "Mapa de Nós",
|
||||
"map_noNodesWithLocation": "Não existem nós com dados de localização.",
|
||||
"map_nodesNeedGps": "Os nós precisam partilhar as suas coordenadas GPS\npara aparecerem no mapa",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Isso também excluirá {count} canal/canais e suas mensagens.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Saiu da comunidade \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Hashtag público (qualquer pessoa pode participar)",
|
||||
"community_communityHashtag": "Hashtag da Comunidade",
|
||||
"community_communityHashtagDesc": "Apenas para membros da comunidade",
|
||||
"community_forCommunity": "Para {name}"
|
||||
"community_forCommunity": "Para {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecretConfirm": "Regenerar a chave secreta para \"{name}\"? Todos os membros precisarão escanear o novo código QR para continuar a comunicação.",
|
||||
"community_regenerateSecret": "Regenerar Senha Segura",
|
||||
"community_secretRegenerated": "Senha secreta regenerada para \"{name}\"",
|
||||
"community_regenerate": "Regenerar",
|
||||
"community_secretUpdated": "Segredo atualizado para \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Scanar o novo código QR para atualizar o segredo para \"{name}\"\n\n\n+++++",
|
||||
"community_updateSecret": "Atualizar Segredo",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Você",
|
||||
"pathTrace_failed": "Falha no rastreamento de caminho.",
|
||||
"pathTrace_notAvailable": "Traçado de caminho não disponível.",
|
||||
"pathTrace_refreshTooltip": "Atualizar Path Trace.",
|
||||
"contacts_pathTrace": "Traçado de Caminho",
|
||||
"contacts_ping": "Pingar",
|
||||
"contacts_repeaterPathTrace": "Traçar caminho para repetidor",
|
||||
"contacts_repeaterPing": "Pingar repetidor",
|
||||
"contacts_roomPathTrace": "Traçar caminho para o servidor da sala",
|
||||
"contacts_roomPing": "Pingar servidor da sala",
|
||||
"contacts_chatTraceRoute": "Rastrear rota do caminho",
|
||||
"contacts_pathTraceTo": "Rastrear rota para {name}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,797 @@
|
||||
{
|
||||
"@@locale": "ru",
|
||||
"appTitle": "MeshCore Open",
|
||||
"nav_contacts": "Контакты",
|
||||
"nav_channels": "Каналы",
|
||||
"nav_map": "Карта",
|
||||
"common_cancel": "Отмена",
|
||||
"common_ok": "OK",
|
||||
"common_connect": "Коннект",
|
||||
"common_unknownDevice": "Неизвестное устройство",
|
||||
"common_save": "Сохранить",
|
||||
"common_delete": "Удалить",
|
||||
"common_close": "Закрыть",
|
||||
"common_edit": "Изменить",
|
||||
"common_add": "Добавить",
|
||||
"common_settings": "Настройки",
|
||||
"common_disconnect": "Отключить",
|
||||
"common_connected": "Подключено",
|
||||
"common_disconnected": "Отключено",
|
||||
"common_create": "Создать",
|
||||
"common_continue": "Продолжить",
|
||||
"common_share": "Поделиться",
|
||||
"common_copy": "Копировать",
|
||||
"common_retry": "Повторить",
|
||||
"common_hide": "Скрыть",
|
||||
"common_remove": "Убрать",
|
||||
"common_enable": "Включить",
|
||||
"common_disable": "Выключить",
|
||||
"common_reboot": "Перезагрузить",
|
||||
"common_loading": "Загрузка...",
|
||||
"common_notAvailable": "—",
|
||||
"common_voltageValue": "{volts} В",
|
||||
"common_percentValue": "{percent}%",
|
||||
"scanner_title": "MeshCore Open",
|
||||
"scanner_scanning": "Поиск устройств...",
|
||||
"scanner_connecting": "Подключение...",
|
||||
"scanner_disconnecting": "Отключение...",
|
||||
"scanner_notConnected": "Не подключено",
|
||||
"scanner_connectedTo": "Подключено к {deviceName}",
|
||||
"scanner_searchingDevices": "Поиск устройств MeshCore...",
|
||||
"scanner_tapToScan": "Нажмите для поиска MeshCore устройств",
|
||||
"scanner_connectionFailed": "Подключение не удалось: {error}",
|
||||
"scanner_stop": "Стоп",
|
||||
"scanner_scan": "Сканирование",
|
||||
"device_quickSwitch": "Быстрое переключение",
|
||||
"device_meshcore": "MeshCore",
|
||||
"settings_title": "Настройки",
|
||||
"settings_deviceInfo": "Информация об устройстве",
|
||||
"settings_appSettings": "Настройки приложения",
|
||||
"settings_appSettingsSubtitle": "Уведомления, сообщения и настройки карты",
|
||||
"settings_nodeSettings": "Настройки ноды",
|
||||
"settings_nodeName": "Имя ноды",
|
||||
"settings_nodeNameNotSet": "Не установлено",
|
||||
"settings_nodeNameHint": "Введите имя ноды",
|
||||
"settings_nodeNameUpdated": "Имя обновлено",
|
||||
"settings_radioSettings": "Настройки радио",
|
||||
"settings_radioSettingsSubtitle": "Частота, мощность и коэффициент распространения",
|
||||
"settings_radioSettingsUpdated": "Настройки радио обновлены",
|
||||
"settings_location": "Позиция",
|
||||
"settings_locationSubtitle": "Координаты GPS",
|
||||
"settings_locationUpdated": "Позиция и настройки GPS обновлены",
|
||||
"settings_locationBothRequired": "Введите широту и долготу.",
|
||||
"settings_locationInvalid": "Неверная широта или долгота.",
|
||||
"settings_locationGPSEnable": "Включить GPS",
|
||||
"settings_locationGPSEnableSubtitle": "Включение GPS для автоматического обновления позиции.",
|
||||
"settings_locationIntervalSec": "Интервал для позиционирования GPS (секунды)",
|
||||
"settings_locationIntervalInvalid": "Интервал должен составлять не менее 60 секунд и не более 86400 секунд.",
|
||||
"settings_latitude": "Широта",
|
||||
"settings_longitude": "Долгота",
|
||||
"settings_privacyMode": "Режим конфиденциальности",
|
||||
"settings_privacyModeSubtitle": "Скрыть имя/позицию в анонсировании",
|
||||
"settings_privacyModeToggle": "Включите режим конфиденциальности, чтобы скрыть свое имя и местоположение в анонсировании.",
|
||||
"settings_privacyModeEnabled": "Режим конфиденциальности включен",
|
||||
"settings_privacyModeDisabled": "Режим конфиденциальности выключен",
|
||||
"settings_actions": "Действия",
|
||||
"settings_sendAdvertisement": "Отправить анонсирование",
|
||||
"settings_sendAdvertisementSubtitle": "Отправить анонсирование о присутствии сейчас",
|
||||
"settings_advertisementSent": "Анонсирование отправлено",
|
||||
"settings_syncTime": "Синхронизация времени",
|
||||
"settings_syncTimeSubtitle": "Синхронизировать время с телефоном",
|
||||
"settings_timeSynchronized": "Время синхронизировано",
|
||||
"settings_refreshContacts": "Обновить контакты",
|
||||
"settings_refreshContactsSubtitle": "Перезагрузить список контактов с устройства",
|
||||
"settings_rebootDevice": "Перезагрузить устройство",
|
||||
"settings_rebootDeviceSubtitle": "Перезапустить устройство MeshCore",
|
||||
"settings_rebootDeviceConfirm": "Вы уверены, что хотите перезагрузить устройство? Вы будете отключены.",
|
||||
"settings_debug": "Отладка",
|
||||
"settings_bleDebugLog": "Журнал отладки BLE",
|
||||
"settings_bleDebugLogSubtitle": "Команды BLE, ответы и сырые данные",
|
||||
"settings_appDebugLog": "Журнал отладки приложения",
|
||||
"settings_appDebugLogSubtitle": "Сообщения отладки приложения",
|
||||
"settings_about": "О программе",
|
||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
||||
"settings_aboutLegalese": "2026 MeshCore Open Source Project",
|
||||
"settings_aboutDescription": "Открытое клиентское приложение на Flutter для устройств MeshCore с LoRa-сетями.",
|
||||
"settings_infoName": "Имя",
|
||||
"settings_infoId": "ID",
|
||||
"settings_infoStatus": "Статус",
|
||||
"settings_infoBattery": "Батарея",
|
||||
"settings_infoPublicKey": "Публичный ключ",
|
||||
"settings_infoContactsCount": "Количество контактов",
|
||||
"settings_infoChannelCount": "Количество каналов",
|
||||
"settings_presets": "Пресеты",
|
||||
"settings_preset915Mhz": "915 МГц",
|
||||
"settings_preset868Mhz": "868 МГц",
|
||||
"settings_preset433Mhz": "433 МГц",
|
||||
"settings_frequency": "Частота (МГц)",
|
||||
"settings_frequencyHelper": "300.0 – 2500.0",
|
||||
"settings_frequencyInvalid": "Недопустимая частота (300–2500 МГц)",
|
||||
"settings_bandwidth": "Полоса пропускания",
|
||||
"settings_spreadingFactor": "Коэффициент расширения",
|
||||
"settings_codingRate": "Коэффициент кодирования",
|
||||
"settings_txPower": "Мощность передачи (дБм)",
|
||||
"settings_txPowerHelper": "0 – 22",
|
||||
"settings_txPowerInvalid": "Недопустимая мощность передачи (0–22 дБм)",
|
||||
"settings_longRange": "Дальний радиус",
|
||||
"settings_fastSpeed": "Высокая скорость",
|
||||
"settings_error": "Ошибка: {message}",
|
||||
"appSettings_title": "Настройки приложения",
|
||||
"appSettings_appearance": "Внешний вид",
|
||||
"appSettings_theme": "Тема",
|
||||
"appSettings_themeSystem": "Как в системе",
|
||||
"appSettings_themeLight": "Светлая",
|
||||
"appSettings_themeDark": "Тёмная",
|
||||
"appSettings_language": "Язык",
|
||||
"appSettings_languageSystem": "Как в системе",
|
||||
"appSettings_languageEn": "Английский",
|
||||
"appSettings_languageFr": "Французский",
|
||||
"appSettings_languageEs": "Испанский",
|
||||
"appSettings_languageDe": "Немецкий",
|
||||
"appSettings_languagePl": "Польский",
|
||||
"appSettings_languageSl": "Словенский",
|
||||
"appSettings_languagePt": "Португальский",
|
||||
"appSettings_languageIt": "Итальянский",
|
||||
"appSettings_languageZh": "Китайский",
|
||||
"appSettings_languageSv": "Шведский",
|
||||
"appSettings_languageNl": "Нидерландский",
|
||||
"appSettings_languageSk": "Словацкий",
|
||||
"appSettings_languageBg": "Болгарский",
|
||||
"appSettings_languageRu": "Русский",
|
||||
"appSettings_notifications": "Уведомления",
|
||||
"appSettings_enableNotifications": "Включить уведомления",
|
||||
"appSettings_enableNotificationsSubtitle": "Получать уведомления о сообщениях и оповещениях",
|
||||
"appSettings_notificationPermissionDenied": "Разрешение на уведомления отклонено",
|
||||
"appSettings_notificationsEnabled": "Уведомления включены",
|
||||
"appSettings_notificationsDisabled": "Уведомления отключены",
|
||||
"appSettings_messageNotifications": "Уведомления о сообщениях",
|
||||
"appSettings_messageNotificationsSubtitle": "Показывать уведомление при получении новых сообщений",
|
||||
"appSettings_channelMessageNotifications": "Уведомления о сообщениях в каналах",
|
||||
"appSettings_channelMessageNotificationsSubtitle": "Показывать уведомление при получении сообщений в каналах",
|
||||
"appSettings_advertisementNotifications": "Уведомления об анонсированиях",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Показывать уведомление при обнаружении новых нод",
|
||||
"appSettings_messaging": "Обмен сообщениями",
|
||||
"appSettings_clearPathOnMaxRetry": "Сбросить маршрут после максимального числа попыток",
|
||||
"appSettings_clearPathOnMaxRetrySubtitle": "Сбросить маршрут контакта после 5 неудачных попыток отправки",
|
||||
"appSettings_pathsWillBeCleared": "Маршруты будут сброшены после 5 неудачных попыток",
|
||||
"appSettings_pathsWillNotBeCleared": "Маршруты не будут автоматически сбрасываться",
|
||||
"appSettings_autoRouteRotation": "Автоматическое переключение маршрутов",
|
||||
"appSettings_autoRouteRotationSubtitle": "Циклически переключаться между лучшими маршрутами и режимом рассылки",
|
||||
"appSettings_autoRouteRotationEnabled": "Автоматическое переключение маршрутов включено",
|
||||
"appSettings_autoRouteRotationDisabled": "Автоматическое переключение маршрутов отключено",
|
||||
"appSettings_battery": "Батарея",
|
||||
"appSettings_batteryChemistry": "Химия батареи",
|
||||
"appSettings_batteryChemistryPerDevice": "Установить для устройства ({deviceName})",
|
||||
"appSettings_batteryChemistryConnectFirst": "Подключитесь к устройству, чтобы выбрать",
|
||||
"appSettings_batteryNmc": "18650 NMC (3.0–4.2 В)",
|
||||
"appSettings_batteryLifepo4": "LiFePO4 (2.6–3.65 В)",
|
||||
"appSettings_batteryLipo": "LiPo (3.0–4.2 В)",
|
||||
"appSettings_mapDisplay": "Отображение карты",
|
||||
"appSettings_showRepeaters": "Показывать репитеры",
|
||||
"appSettings_showRepeatersSubtitle": "Отображать репитеры на карте",
|
||||
"appSettings_showChatNodes": "Показывать чат-ноды",
|
||||
"appSettings_showChatNodesSubtitle": "Отображать чат-ноды на карте",
|
||||
"appSettings_showOtherNodes": "Показывать другие ноды",
|
||||
"appSettings_showOtherNodesSubtitle": "Отображать другие типы нод на карте",
|
||||
"appSettings_timeFilter": "Фильтр по времени",
|
||||
"appSettings_timeFilterShowAll": "Показывать все ноды",
|
||||
"appSettings_timeFilterShowLast": "Показывать ноды за последние {hours} ч",
|
||||
"appSettings_mapTimeFilter": "Временной фильтр карты",
|
||||
"appSettings_showNodesDiscoveredWithin": "Показывать ноды, обнаруженные за:",
|
||||
"appSettings_allTime": "Всё время",
|
||||
"appSettings_lastHour": "Последний час",
|
||||
"appSettings_last6Hours": "Последние 6 часов",
|
||||
"appSettings_last24Hours": "Последние 24 часа",
|
||||
"appSettings_lastWeek": "Последнюю неделю",
|
||||
"appSettings_offlineMapCache": "Кэш офлайн-карты",
|
||||
"appSettings_noAreaSelected": "Область не выбрана",
|
||||
"appSettings_areaSelectedZoom": "Область выбрана (масштаб {minZoom}–{maxZoom})",
|
||||
"appSettings_debugCard": "Отладка",
|
||||
"appSettings_appDebugLogging": "Журнал отладки приложения",
|
||||
"appSettings_appDebugLoggingSubtitle": "Записывать отладочные сообщения приложения для диагностики",
|
||||
"appSettings_appDebugLoggingEnabled": "Журнал отладки приложения включён",
|
||||
"appSettings_appDebugLoggingDisabled": "Журнал отладки приложения отключён",
|
||||
"contacts_title": "Контакты",
|
||||
"contacts_noContacts": "Контактов пока нет",
|
||||
"contacts_contactsWillAppear": "Контакты появятся, когда устройства начнут рассылать оповещения",
|
||||
"contacts_searchContacts": "Поиск контактов...",
|
||||
"contacts_noUnreadContacts": "Нет непрочитанных контактов",
|
||||
"contacts_noContactsFound": "Контакты или группы не найдены",
|
||||
"contacts_deleteContact": "Удалить контакт",
|
||||
"contacts_removeConfirm": "Удалить {contactName} из контактов?",
|
||||
"contacts_manageRepeater": "Управление репитером",
|
||||
"contacts_manageRoom": "Управление сервером комнат",
|
||||
"contacts_roomLogin": "Вход на сервер комнат",
|
||||
"contacts_openChat": "Открыть чат",
|
||||
"contacts_editGroup": "Изменить группу",
|
||||
"contacts_deleteGroup": "Удалить группу",
|
||||
"contacts_deleteGroupConfirm": "Удалить \"{groupName}\"?",
|
||||
"contacts_newGroup": "Новая группа",
|
||||
"contacts_groupName": "Имя группы",
|
||||
"contacts_groupNameRequired": "Имя группы обязательно",
|
||||
"contacts_groupAlreadyExists": "Группа \"{name}\" уже существует",
|
||||
"contacts_filterContacts": "Фильтр контактов...",
|
||||
"contacts_noContactsMatchFilter": "Нет контактов, соответствующих фильтру",
|
||||
"contacts_noMembers": "Нет участников",
|
||||
"contacts_lastSeenNow": "Видели только что",
|
||||
"contacts_lastSeenMinsAgo": "Видели {minutes} мин назад",
|
||||
"contacts_lastSeenHourAgo": "Видели 1 час назад",
|
||||
"contacts_lastSeenHoursAgo": "Видели {hours} ч назад",
|
||||
"contacts_lastSeenDayAgo": "Видели 1 день назад",
|
||||
"contacts_lastSeenDaysAgo": "Видели {days} дн. назад",
|
||||
"channels_title": "Каналы",
|
||||
"channels_noChannelsConfigured": "Каналы не настроены",
|
||||
"channels_addPublicChannel": "Добавить публичный канал",
|
||||
"channels_searchChannels": "Поиск каналов...",
|
||||
"channels_noChannelsFound": "Каналы не найдены",
|
||||
"channels_channelIndex": "Канал {index}",
|
||||
"channels_hashtagChannel": "Хэштег-канал",
|
||||
"channels_public": "Публичный",
|
||||
"channels_private": "Приватный",
|
||||
"channels_publicChannel": "Публичный канал",
|
||||
"channels_privateChannel": "Приватный канал",
|
||||
"channels_editChannel": "Изменить канал",
|
||||
"channels_deleteChannel": "Удалить канал",
|
||||
"channels_deleteChannelConfirm": "Удалить \"{name}\"? Это действие нельзя отменить.",
|
||||
"channels_channelDeleted": "Канал \"{name}\" удалён",
|
||||
"channels_addChannel": "Добавить канал",
|
||||
"channels_channelIndexLabel": "Индекс канала",
|
||||
"channels_channelName": "Имя канала",
|
||||
"channels_usePublicChannel": "Использовать публичный канал",
|
||||
"channels_standardPublicPsk": "Стандартный публичный PSK",
|
||||
"channels_pskHex": "PSK (Hex)",
|
||||
"channels_generateRandomPsk": "Сгенерировать случайный PSK",
|
||||
"channels_enterChannelName": "Введите имя канала",
|
||||
"channels_pskMustBe32Hex": "PSK должен содержать 32 шестнадцатеричных символа",
|
||||
"channels_channelAdded": "Канал \"{name}\" добавлен",
|
||||
"channels_editChannelTitle": "Изменить канал {index}",
|
||||
"channels_smazCompression": "Сжатие SMAZ",
|
||||
"channels_channelUpdated": "Канал \"{name}\" обновлён",
|
||||
"channels_publicChannelAdded": "Публичный канал добавлен",
|
||||
"channels_sortBy": "Сортировка",
|
||||
"channels_sortManual": "Вручную",
|
||||
"channels_sortAZ": "По алфавиту",
|
||||
"channels_sortLatestMessages": "По последним сообщениям",
|
||||
"channels_sortUnread": "По непрочитанным",
|
||||
"channels_createPrivateChannel": "Создать приватный канал",
|
||||
"channels_createPrivateChannelDesc": "Защищён секретным ключом.",
|
||||
"channels_joinPrivateChannel": "Присоединиться к приватному каналу",
|
||||
"channels_joinPrivateChannelDesc": "Введите секретный ключ вручную.",
|
||||
"channels_joinPublicChannel": "Присоединиться к публичному каналу",
|
||||
"channels_joinPublicChannelDesc": "К этому каналу может присоединиться любой.",
|
||||
"channels_joinHashtagChannel": "Присоединиться к хэштег-каналу",
|
||||
"channels_joinHashtagChannelDesc": "К хэштег-каналам может присоединиться любой.",
|
||||
"channels_scanQrCode": "Сканировать QR-код",
|
||||
"channels_scanQrCodeComingSoon": "Скоро будет",
|
||||
"channels_enterHashtag": "Введите хэштег",
|
||||
"channels_hashtagHint": "например, #команда",
|
||||
"chat_noMessages": "Сообщений пока нет",
|
||||
"chat_sendMessageToStart": "Отправьте сообщение, чтобы начать",
|
||||
"chat_originalMessageNotFound": "Исходное сообщение не найдено",
|
||||
"chat_replyingTo": "Ответ для {name}",
|
||||
"chat_replyTo": "Ответить {name}",
|
||||
"chat_location": "Местоположение",
|
||||
"chat_sendMessageTo": "Отправить сообщение {contactName}",
|
||||
"chat_typeMessage": "Напишите сообщение...",
|
||||
"chat_messageTooLong": "Сообщение слишком длинное (макс. {maxBytes} байт).",
|
||||
"chat_messageCopied": "Сообщение скопировано",
|
||||
"chat_messageDeleted": "Сообщение удалено",
|
||||
"chat_retryingMessage": "Повтор отправки сообщения",
|
||||
"chat_retryCount": "Попытка {current}/{max}",
|
||||
"chat_sendGif": "Отправить GIF",
|
||||
"chat_reply": "Ответить",
|
||||
"chat_addReaction": "Добавить реакцию",
|
||||
"chat_me": "Я",
|
||||
"emojiCategorySmileys": "Смайлы",
|
||||
"emojiCategoryGestures": "Жесты",
|
||||
"emojiCategoryHearts": "Сердечки",
|
||||
"emojiCategoryObjects": "Предметы",
|
||||
"gifPicker_title": "Выберите GIF",
|
||||
"gifPicker_searchHint": "Поиск GIF...",
|
||||
"gifPicker_poweredBy": "Работает на GIPHY",
|
||||
"gifPicker_noGifsFound": "GIF не найдены",
|
||||
"gifPicker_failedLoad": "Не удалось загрузить GIF",
|
||||
"gifPicker_failedSearch": "Не удалось выполнить поиск GIF",
|
||||
"gifPicker_noInternet": "Нет подключения к интернету",
|
||||
"debugLog_appTitle": "Журнал отладки приложения",
|
||||
"debugLog_bleTitle": "Журнал отладки BLE",
|
||||
"debugLog_copyLog": "Копировать журнал",
|
||||
"debugLog_clearLog": "Очистить журнал",
|
||||
"debugLog_copied": "Журнал отладки скопирован",
|
||||
"debugLog_bleCopied": "Журнал BLE скопирован",
|
||||
"debugLog_noEntries": "Журнал отладки пока пуст",
|
||||
"debugLog_enableInSettings": "Включите запись журнала отладки в настройках",
|
||||
"debugLog_frames": "Фреймы",
|
||||
"debugLog_rawLogRx": "Сырой журнал приёма",
|
||||
"debugLog_noBleActivity": "Активность BLE пока отсутствует",
|
||||
"debugFrame_length": "Длина фрейма: {count} байт",
|
||||
"debugFrame_command": "Команда: 0x{value}",
|
||||
"debugFrame_textMessageHeader": "Фрейм текстового сообщения:",
|
||||
"debugFrame_destinationPubKey": "- Публичный ключ получателя: {pubKey}",
|
||||
"debugFrame_timestamp": "- Временная метка: {timestamp}",
|
||||
"debugFrame_flags": "- Флаги: 0x{value}",
|
||||
"debugFrame_textType": "- Тип текста: {type} ({label})",
|
||||
"debugFrame_textTypeCli": "CLI",
|
||||
"debugFrame_textTypePlain": "Обычный",
|
||||
"debugFrame_text": "- Текст: \"{text}\"",
|
||||
"debugFrame_hexDump": "Шестнадцатеричный дамп:",
|
||||
"chat_pathManagement": "Управление маршрутами",
|
||||
"chat_routingMode": "Режим маршрутизации",
|
||||
"chat_autoUseSavedPath": "Авто (использовать сохранённый маршрут)",
|
||||
"chat_forceFloodMode": "Принудительный режим рассылки",
|
||||
"chat_recentAckPaths": "Недавние подтверждённые маршруты (нажмите, чтобы использовать):",
|
||||
"chat_pathHistoryFull": "История маршрутов заполнена. Удалите записи, чтобы добавить новые.",
|
||||
"chat_hopSingular": "хоп",
|
||||
"chat_hopPlural": "хопов",
|
||||
"chat_hopsCount": "{count} {count, plural, one{хоп} few{хопа} many{хопов} other{хопов}}",
|
||||
"chat_successes": "успешно",
|
||||
"chat_removePath": "Удалить маршрут",
|
||||
"chat_noPathHistoryYet": "История маршрутов пока пуста.\nОтправьте сообщение, чтобы обнаружить маршруты.",
|
||||
"chat_pathActions": "Действия с маршрутом:",
|
||||
"chat_setCustomPath": "Указать маршрут вручную",
|
||||
"chat_setCustomPathSubtitle": "Вручную задать маршрут передачи",
|
||||
"chat_clearPath": "Очистить маршрут",
|
||||
"chat_clearPathSubtitle": "Принудительно обновить маршрут при следующей отправке",
|
||||
"chat_pathCleared": "Маршрут очищен. Следующее сообщение обновит маршрут.",
|
||||
"chat_floodModeSubtitle": "Используйте переключатель маршрутизации в панели приложения",
|
||||
"chat_floodModeEnabled": "Режим рассылки включён. Отключите через значок маршрутизации в панели приложения.",
|
||||
"chat_fullPath": "Полный маршрут",
|
||||
"chat_pathDetailsNotAvailable": "Детали маршрута ещё недоступны. Попробуйте отправить сообщение для обновления.",
|
||||
"chat_pathSetHops": "Маршрут установлен: {hopCount} {hopCount, plural, one{хоп} few{хопа} many{хопов} other{хопов}} — {status}",
|
||||
"chat_pathSavedLocally": "Сохранено локально. Подключитесь для синхронизации.",
|
||||
"chat_pathDeviceConfirmed": "Подтверждено устройством.",
|
||||
"chat_pathDeviceNotConfirmed": "Ещё не подтверждено устройством.",
|
||||
"chat_type": "Тип",
|
||||
"chat_path": "Маршрут",
|
||||
"chat_publicKey": "Публичный ключ",
|
||||
"chat_compressOutgoingMessages": "Сжимать исходящие сообщения",
|
||||
"chat_floodForced": "Рассылка (принудительно)",
|
||||
"chat_directForced": "Прямой (принудительно)",
|
||||
"chat_hopsForced": "{count} хоп(ов) (принудительно)",
|
||||
"chat_floodAuto": "Рассылка (авто)",
|
||||
"chat_direct": "Прямой",
|
||||
"chat_poiShared": "Точка интереса отправлена",
|
||||
"chat_unread": "Непрочитанных: {count}",
|
||||
"map_title": "Карта нод",
|
||||
"map_noNodesWithLocation": "Нет нод с данными о местоположении",
|
||||
"map_nodesNeedGps": "Ноды должны передавать свои GPS-координаты, чтобы отображаться на карте",
|
||||
"map_nodesCount": "Нод: {count}",
|
||||
"map_pinsCount": "Меток: {count}",
|
||||
"map_chat": "Чат",
|
||||
"map_repeater": "Репитер",
|
||||
"map_room": "Комната",
|
||||
"map_sensor": "Сенсор",
|
||||
"map_pinDm": "Метка (ЛС)",
|
||||
"map_pinPrivate": "Метка (Приватная)",
|
||||
"map_pinPublic": "Метка (Публичная)",
|
||||
"map_lastSeen": "Последнее появление",
|
||||
"map_disconnectConfirm": "Вы уверены, что хотите отключиться от этого устройства?",
|
||||
"map_from": "От",
|
||||
"map_source": "Источник",
|
||||
"map_flags": "Флаги",
|
||||
"map_shareMarkerHere": "Поделиться меткой здесь",
|
||||
"map_pinLabel": "Метка",
|
||||
"map_label": "Подпись",
|
||||
"map_pointOfInterest": "Точка интереса",
|
||||
"map_sendToContact": "Отправить контакту",
|
||||
"map_sendToChannel": "Отправить в канал",
|
||||
"map_noChannelsAvailable": "Нет доступных каналов",
|
||||
"map_publicLocationShare": "Публичная передача местоположения",
|
||||
"map_publicLocationShareConfirm": "Вы собираетесь поделиться местоположением в {channelLabel}. Этот канал публичный, и любой, у кого есть PSK, сможет его увидеть.",
|
||||
"map_connectToShareMarkers": "Подключитесь к устройству, чтобы делиться метками",
|
||||
"map_filterNodes": "Фильтр нод",
|
||||
"map_nodeTypes": "Типы нод",
|
||||
"map_chatNodes": "Чат-ноды",
|
||||
"map_repeaters": "Репитеры",
|
||||
"map_otherNodes": "Другие ноды",
|
||||
"map_keyPrefix": "Префикс ключа",
|
||||
"map_filterByKeyPrefix": "Фильтр по префиксу ключа",
|
||||
"map_publicKeyPrefix": "Префикс публичного ключа",
|
||||
"map_markers": "Метки",
|
||||
"map_showSharedMarkers": "Показывать общие метки",
|
||||
"map_lastSeenTime": "Время последнего появления",
|
||||
"map_sharedPin": "Общая метка",
|
||||
"map_joinRoom": "Присоединиться к комнате",
|
||||
"map_manageRepeater": "Управление репитером",
|
||||
"mapCache_title": "Кэш офлайн-карты",
|
||||
"mapCache_selectAreaFirst": "Сначала выберите область для кэширования",
|
||||
"mapCache_noTilesToDownload": "Нет плиток для загрузки в этой области",
|
||||
"mapCache_downloadTilesTitle": "Загрузить плитки",
|
||||
"mapCache_downloadTilesPrompt": "Загрузить {count} плиток для офлайн-использования?",
|
||||
"mapCache_downloadAction": "Загрузить",
|
||||
"mapCache_cachedTiles": "Закэшировано {count} плиток",
|
||||
"mapCache_cachedTilesWithFailed": "Закэшировано {downloaded} плиток ({failed} не загружено)",
|
||||
"mapCache_clearOfflineCacheTitle": "Очистить офлайн-кэш",
|
||||
"mapCache_clearOfflineCachePrompt": "Удалить все закэшированные плитки карты?",
|
||||
"mapCache_offlineCacheCleared": "Офлайн-кэш очищен",
|
||||
"mapCache_noAreaSelected": "Область не выбрана",
|
||||
"mapCache_cacheArea": "Область кэширования",
|
||||
"mapCache_useCurrentView": "Использовать текущий вид",
|
||||
"mapCache_zoomRange": "Диапазон масштаба",
|
||||
"mapCache_estimatedTiles": "Оценочное количество плиток: {count}",
|
||||
"mapCache_downloadedTiles": "Загружено {completed} из {total}",
|
||||
"mapCache_downloadTilesButton": "Загрузить плитки",
|
||||
"mapCache_clearCacheButton": "Очистить кэш",
|
||||
"mapCache_failedDownloads": "Неудачных загрузок: {count}",
|
||||
"mapCache_boundsLabel": "С {north}, Ю {south}, В {east}, З {west}",
|
||||
"time_justNow": "Только что",
|
||||
"time_minutesAgo": "{minutes} мин назад",
|
||||
"time_hoursAgo": "{hours} ч назад",
|
||||
"time_daysAgo": "{days} дн. назад",
|
||||
"time_hour": "час",
|
||||
"time_hours": "часов",
|
||||
"time_day": "день",
|
||||
"time_days": "дней",
|
||||
"time_week": "неделя",
|
||||
"time_weeks": "недель",
|
||||
"time_month": "месяц",
|
||||
"time_months": "месяцев",
|
||||
"time_minutes": "минут",
|
||||
"time_allTime": "Всё время",
|
||||
"dialog_disconnect": "Отключиться",
|
||||
"dialog_disconnectConfirm": "Вы уверены, что хотите отключиться от этого устройства?",
|
||||
"login_repeaterLogin": "Вход в репитер",
|
||||
"login_roomLogin": "Вход на сервер комнат",
|
||||
"login_password": "Пароль",
|
||||
"login_enterPassword": "Введите пароль",
|
||||
"login_savePassword": "Сохранить пароль",
|
||||
"login_savePasswordSubtitle": "Пароль будет надёжно сохранён на этом устройстве",
|
||||
"login_repeaterDescription": "Введите пароль репитера для доступа к настройкам и статусу.",
|
||||
"login_roomDescription": "Введите пароль комнаты для доступа к настройкам и статусу.",
|
||||
"login_routing": "Маршрутизация",
|
||||
"login_routingMode": "Режим маршрутизации",
|
||||
"login_autoUseSavedPath": "Авто (использовать сохранённый маршрут)",
|
||||
"login_forceFloodMode": "Принудительный режим рассылки",
|
||||
"login_managePaths": "Управление маршрутами",
|
||||
"login_login": "Войти",
|
||||
"login_attempt": "Попытка {current}/{max}",
|
||||
"login_failed": "Ошибка входа: {error}",
|
||||
"login_failedMessage": "Не удалось войти. Либо пароль неверен, либо репитер недоступен.",
|
||||
"common_reload": "Обновить",
|
||||
"common_clear": "Очистить",
|
||||
"path_currentPath": "Текущий маршрут: {path}",
|
||||
"path_usingHopsPath": "Используется маршрут из {count} {count, plural, one{хоп} few{хопа} many{хопов} other{хопов}}",
|
||||
"path_enterCustomPath": "Введите маршрут вручную",
|
||||
"path_currentPathLabel": "Текущий маршрут",
|
||||
"path_hexPrefixInstructions": "Введите 2-символьные шестнадцатеричные префиксы для каждого хопа, разделённые запятыми.",
|
||||
"path_hexPrefixExample": "Пример: A1,F2,3C (каждый узел использует первый байт своего публичного ключа)",
|
||||
"path_labelHexPrefixes": "Маршрут (шестнадцатеричные префиксы)",
|
||||
"path_helperMaxHops": "Максимум 64 хопа. Каждый префикс — 2 шестнадцатеричных символа (1 байт)",
|
||||
"path_selectFromContacts": "Или выберите из контактов:",
|
||||
"path_noRepeatersFound": "Репитеры или серверы комнат не найдены.",
|
||||
"path_customPathsRequire": "Пользовательские маршруты требуют промежуточных узлов, способных ретранслировать сообщения.",
|
||||
"path_invalidHexPrefixes": "Недопустимые шестнадцатеричные префиксы: {prefixes}",
|
||||
"path_tooLong": "Маршрут слишком длинный. Максимум 64 хопа.",
|
||||
"path_setPath": "Установить маршрут",
|
||||
"repeater_management": "Управление репитером",
|
||||
"room_management": "Управление сервером комнат",
|
||||
"repeater_managementTools": "Инструменты управления",
|
||||
"repeater_status": "Статус",
|
||||
"repeater_statusSubtitle": "Просмотр статуса, статистики и соседей репитера",
|
||||
"repeater_telemetry": "Телеметрия",
|
||||
"repeater_telemetrySubtitle": "Просмотр телеметрии датчиков и системной статистики",
|
||||
"repeater_cli": "CLI",
|
||||
"repeater_cliSubtitle": "Отправка команд репитеру",
|
||||
"repeater_neighbours": "Соседи",
|
||||
"repeater_neighboursSubtitle": "Просмотр соседей на нулевом хопе.",
|
||||
"repeater_settings": "Настройки",
|
||||
"repeater_settingsSubtitle": "Настройка параметров репитера",
|
||||
"repeater_statusTitle": "Статус репитера",
|
||||
"repeater_routingMode": "Режим маршрутизации",
|
||||
"repeater_autoUseSavedPath": "Авто (использовать сохранённый маршрут)",
|
||||
"repeater_forceFloodMode": "Принудительный режим рассылки",
|
||||
"repeater_pathManagement": "Управление маршрутами",
|
||||
"repeater_refresh": "Обновить",
|
||||
"repeater_statusRequestTimeout": "Время ожидания статуса истекло.",
|
||||
"repeater_errorLoadingStatus": "Ошибка загрузки статуса: {error}",
|
||||
"repeater_systemInformation": "Системная информация",
|
||||
"repeater_battery": "Батарея",
|
||||
"repeater_clockAtLogin": "Время (при входе)",
|
||||
"repeater_uptime": "Время работы",
|
||||
"repeater_queueLength": "Длина очереди",
|
||||
"repeater_debugFlags": "Флаги отладки",
|
||||
"repeater_radioStatistics": "Радиостатистика",
|
||||
"repeater_lastRssi": "Последний RSSI",
|
||||
"repeater_lastSnr": "Последний SNR",
|
||||
"repeater_noiseFloor": "Уровень шума",
|
||||
"repeater_txAirtime": "Время эфира (передача)",
|
||||
"repeater_rxAirtime": "Время эфира (приём)",
|
||||
"repeater_packetStatistics": "Статистика пакетов",
|
||||
"repeater_sent": "Отправлено",
|
||||
"repeater_received": "Получено",
|
||||
"repeater_duplicates": "Дубликаты",
|
||||
"repeater_daysHoursMinsSecs": "{days} дн. {hours}ч {minutes}м {seconds}с",
|
||||
"repeater_packetTxTotal": "Всего: {total}, Рассылка: {flood}, Прямые: {direct}",
|
||||
"repeater_packetRxTotal": "Всего: {total}, Рассылка: {flood}, Прямые: {direct}",
|
||||
"repeater_duplicatesFloodDirect": "Рассылка: {flood}, Прямые: {direct}",
|
||||
"repeater_duplicatesTotal": "Всего: {total}",
|
||||
"repeater_settingsTitle": "Настройки репитера",
|
||||
"repeater_basicSettings": "Основные настройки",
|
||||
"repeater_repeaterName": "Имя репитера",
|
||||
"repeater_repeaterNameHelper": "Отображаемое имя этого репитера",
|
||||
"repeater_adminPassword": "Пароль администратора",
|
||||
"repeater_adminPasswordHelper": "Пароль с полным доступом",
|
||||
"repeater_guestPassword": "Гостевой пароль",
|
||||
"repeater_guestPasswordHelper": "Пароль для доступа только для чтения",
|
||||
"repeater_radioSettings": "Настройки радио",
|
||||
"repeater_frequencyMhz": "Частота (МГц)",
|
||||
"repeater_frequencyHelper": "300–2500 МГц",
|
||||
"repeater_txPower": "Мощность передачи",
|
||||
"repeater_txPowerHelper": "1–30 дБм",
|
||||
"repeater_bandwidth": "Полоса пропускания",
|
||||
"repeater_spreadingFactor": "Коэффициент расширения",
|
||||
"repeater_codingRate": "Коэффициент кодирования",
|
||||
"repeater_locationSettings": "Настройки местоположения",
|
||||
"repeater_latitude": "Широта",
|
||||
"repeater_latitudeHelper": "В десятичных градусах (напр., 37.7749)",
|
||||
"repeater_longitude": "Долгота",
|
||||
"repeater_longitudeHelper": "В десятичных градусах (напр., -122.4194)",
|
||||
"repeater_features": "Функции",
|
||||
"repeater_packetForwarding": "Пересылка пакетов",
|
||||
"repeater_packetForwardingSubtitle": "Разрешить репитеру пересылать пакеты",
|
||||
"repeater_guestAccess": "Гостевой доступ",
|
||||
"repeater_guestAccessSubtitle": "Разрешить гостевой доступ только для чтения",
|
||||
"repeater_privacyMode": "Режим конфиденциальности",
|
||||
"repeater_privacyModeSubtitle": "Скрывать имя/местоположение в оповещениях",
|
||||
"repeater_advertisementSettings": "Настройки анонсирования",
|
||||
"repeater_localAdvertInterval": "Интервал локальных анонсирований",
|
||||
"repeater_localAdvertIntervalMinutes": "{minutes} минут",
|
||||
"repeater_floodAdvertInterval": "Интервал анонсирований рассылкой (flood)",
|
||||
"repeater_floodAdvertIntervalHours": "{hours} часов",
|
||||
"repeater_encryptedAdvertInterval": "Интервал зашифрованных анонсирований",
|
||||
"repeater_dangerZone": "Опасная зона",
|
||||
"repeater_rebootRepeater": "Перезагрузить репитер",
|
||||
"repeater_rebootRepeaterSubtitle": "Перезапустить устройство репитера",
|
||||
"repeater_rebootRepeaterConfirm": "Вы уверены, что хотите перезагрузить этот репитер?",
|
||||
"repeater_regenerateIdentityKey": "Пересоздать ключ идентификации",
|
||||
"repeater_regenerateIdentityKeySubtitle": "Сгенерировать новую пару публичного/приватного ключей",
|
||||
"repeater_regenerateIdentityKeyConfirm": "Это создаст новую идентичность для репитера. Продолжить?",
|
||||
"repeater_eraseFileSystem": "Стереть файловую систему",
|
||||
"repeater_eraseFileSystemSubtitle": "Отформатировать файловую систему репитера",
|
||||
"repeater_eraseFileSystemConfirm": "ВНИМАНИЕ: это удалит все данные на репитере. Действие нельзя отменить!",
|
||||
"repeater_eraseSerialOnly": "Очистка доступна только через последовательную консоль.",
|
||||
"repeater_commandSent": "Команда отправлена: {command}",
|
||||
"repeater_errorSendingCommand": "Ошибка отправки команды: {error}",
|
||||
"repeater_confirm": "Подтвердить",
|
||||
"repeater_settingsSaved": "Настройки успешно сохранены",
|
||||
"repeater_errorSavingSettings": "Ошибка сохранения настроек: {error}",
|
||||
"repeater_refreshBasicSettings": "Обновить основные настройки",
|
||||
"repeater_refreshRadioSettings": "Обновить настройки радио",
|
||||
"repeater_refreshTxPower": "Обновить мощность передачи",
|
||||
"repeater_refreshLocationSettings": "Обновить настройки местоположения",
|
||||
"repeater_refreshPacketForwarding": "Обновить пересылку пакетов",
|
||||
"repeater_refreshGuestAccess": "Обновить гостевой доступ",
|
||||
"repeater_refreshPrivacyMode": "Обновить режим конфиденциальности",
|
||||
"repeater_refreshAdvertisementSettings": "Обновить настройки анонсирований",
|
||||
"repeater_refreshed": "{label} обновлён",
|
||||
"repeater_errorRefreshing": "Ошибка обновления {label}",
|
||||
"repeater_cliTitle": "CLI репитера",
|
||||
"repeater_debugNextCommand": "Отладка следующей команды",
|
||||
"repeater_commandHelp": "Справка по командам",
|
||||
"repeater_clearHistory": "Очистить историю",
|
||||
"repeater_noCommandsSent": "Команды ещё не отправлялись",
|
||||
"repeater_typeCommandOrUseQuick": "Введите команду ниже или используйте быстрые команды",
|
||||
"repeater_enterCommandHint": "Введите команду...",
|
||||
"repeater_previousCommand": "Предыдущая команда",
|
||||
"repeater_nextCommand": "Следующая команда",
|
||||
"repeater_enterCommandFirst": "Сначала введите команду",
|
||||
"repeater_cliCommandFrameTitle": "Фрейм CLI-команды",
|
||||
"repeater_cliCommandError": "Ошибка: {error}",
|
||||
"repeater_cliQuickGetName": "Получить имя",
|
||||
"repeater_cliQuickGetRadio": "Получить радио",
|
||||
"repeater_cliQuickGetTx": "Получить TX",
|
||||
"repeater_cliQuickNeighbors": "Соседи",
|
||||
"repeater_cliQuickVersion": "Версия",
|
||||
"repeater_cliQuickAdvertise": "Анонсировать",
|
||||
"repeater_cliQuickClock": "Время",
|
||||
"repeater_cliHelpAdvert": "Отправляет пакет анонсирования",
|
||||
"repeater_cliHelpReboot": "Перезагружает устройство. (обычно вы получите «Тайм-аут» — это нормально)",
|
||||
"repeater_cliHelpClock": "Показывает текущее время по часам устройства.",
|
||||
"repeater_cliHelpPassword": "Устанавливает новый пароль администратора для устройства.",
|
||||
"repeater_cliHelpVersion": "Показывает версию устройства и дату сборки прошивки.",
|
||||
"repeater_cliHelpClearStats": "Сбрасывает различные счётчики статистики в ноль.",
|
||||
"repeater_cliHelpSetAf": "Устанавливает коэффициент времени в эфире.",
|
||||
"repeater_cliHelpSetTx": "Устанавливает мощность передачи LoRa в дБм. (требуется перезагрузка)",
|
||||
"repeater_cliHelpSetRepeat": "Включает или отключает роль репитера для этой ноды.",
|
||||
"repeater_cliHelpSetAllowReadOnly": "(Сервер комнат) Если «on», то вход без пароля разрешён, но публиковать в комнату нельзя (только чтение)",
|
||||
"repeater_cliHelpSetFloodMax": "Устанавливает максимальное число хопов для входящих пакетов в режиме рассылки (если >= макс., пакет не пересылается)",
|
||||
"repeater_cliHelpSetIntThresh": "Устанавливает порог интерференции (в дБ). По умолчанию 14. Установите 0, чтобы отключить обнаружение помех.",
|
||||
"repeater_cliHelpSetAgcResetInterval": "Устанавливает интервал сброса автоматической регулировки усиления. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetMultiAcks": "Включает или отключает функцию «двойных ACK».",
|
||||
"repeater_cliHelpSetAdvertInterval": "Устанавливает интервал (в минутах) отправки локального (нулевой хоп) анонсирования. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetFloodAdvertInterval": "Устанавливает интервал (в часах) отправки анонсирований рассылкой. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetGuestPassword": "Устанавливает/обновляет гостевой пароль. (для репитеров гости могут отправлять запрос «Get Stats»)",
|
||||
"repeater_cliHelpSetName": "Устанавливает имя в оповещениях.",
|
||||
"repeater_cliHelpSetLat": "Устанавливает широту для карты в оповещениях. (десятичные градусы)",
|
||||
"repeater_cliHelpSetLon": "Устанавливает долготу для карты в оповещениях. (десятичные градусы)",
|
||||
"repeater_cliHelpSetRadio": "Устанавливает полностью новые параметры радио и сохраняет их в настройки. Требуется команда «reboot» для применения.",
|
||||
"repeater_cliHelpSetRxDelay": "Устанавливает (экспериментально) базовую задержку (>1 для эффекта) для принятых пакетов на основе качества сигнала. Установите 0, чтобы отключить.",
|
||||
"repeater_cliHelpSetTxDelay": "Устанавливает множитель времени в эфире для пакета в режиме рассылки и применяет случайную задержку перед пересылкой (чтобы уменьшить коллизии).",
|
||||
"repeater_cliHelpSetDirectTxDelay": "То же, что txdelay, но для случайной задержки пересылки пакетов в прямом режиме.",
|
||||
"repeater_cliHelpSetBridgeEnabled": "Включить/выключить мост.",
|
||||
"repeater_cliHelpSetBridgeDelay": "Установить задержку перед ретрансляцией пакетов.",
|
||||
"repeater_cliHelpSetBridgeSource": "Выбрать, будет ли мост ретранслировать полученные или отправленные пакеты.",
|
||||
"repeater_cliHelpSetBridgeBaud": "Установить скорость последовательного соединения для мостов RS232.",
|
||||
"repeater_cliHelpSetBridgeSecret": "Установить секрет моста для мостов ESP-NOW.",
|
||||
"repeater_cliHelpSetAdcMultiplier": "Устанавливает пользовательский коэффициент коррекции напряжения батареи (поддерживается только на некоторых платах).",
|
||||
"repeater_cliHelpTempRadio": "Устанавливает временные параметры радио на заданное число минут, затем возвращает исходные. (НЕ сохраняется в настройки).",
|
||||
"repeater_cliHelpSetPerm": "Изменяет ACL. Удаляет запись (по префиксу публичного ключа), если «permissions» равен нулю. Добавляет новую запись, если указан полный ключ и он отсутствует в ACL. Обновляет запись по совпадению префикса. Биты прав зависят от роли прошивки, но младшие 2 бита: 0 (Гость), 1 (Только чтение), 2 (Чтение/запись), 3 (Админ)",
|
||||
"repeater_cliHelpGetBridgeType": "Получает тип моста: none, rs232, espnow",
|
||||
"repeater_cliHelpLogStart": "Начинает запись пакетов в файловую систему.",
|
||||
"repeater_cliHelpLogStop": "Останавливает запись пакетов в файловую систему.",
|
||||
"repeater_cliHelpLogErase": "Удаляет журналы пакетов из файловой системы.",
|
||||
"repeater_cliHelpNeighbors": "Показывает список других репитеров, услышанных через оповещения нулевого хопа. Каждая строка: префикс-id-в-hex:временная-метка:snr×4",
|
||||
"repeater_cliHelpNeighborRemove": "Удаляет первую подходящую запись (по префиксу публичного ключа в hex) из списка соседей.",
|
||||
"repeater_cliHelpRegion": "(только через последовательный порт) Показывает все определённые регионы и текущие права на рассылку.",
|
||||
"repeater_cliHelpRegionLoad": "ПРИМЕЧАНИЕ: это специальная многострочная команда. Каждая следующая строка — имя региона (с отступом пробелами для указания иерархии, минимум один пробел). Завершается пустой строкой.",
|
||||
"repeater_cliHelpRegionGet": "Ищет регион по префиксу имени (или «*» для глобальной области). Отвечает: «-> имя-региона (родитель) 'F'»",
|
||||
"repeater_cliHelpRegionPut": "Добавляет или обновляет определение региона с заданным именем.",
|
||||
"repeater_cliHelpRegionRemove": "Удаляет определение региона с заданным именем. (должно точно совпадать и не иметь дочерних регионов)",
|
||||
"repeater_cliHelpRegionAllowf": "Разрешает рассылку («F»lood) для заданного региона. («*» для глобальной/устаревшей области)",
|
||||
"repeater_cliHelpRegionDenyf": "Запрещает рассылку («F»lood) для заданного региона. (НЕ рекомендуется для глобальной области!)",
|
||||
"repeater_cliHelpRegionHome": "Показывает текущий «домашний» регион. (Пока не используется, зарезервировано на будущее)",
|
||||
"repeater_cliHelpRegionHomeSet": "Устанавливает «домашний» регион.",
|
||||
"repeater_cliHelpRegionSave": "Сохраняет список/карту регионов в память.",
|
||||
"repeater_cliHelpGps": "Показывает статус GPS. Если GPS выключен — отвечает только «off». Если включён — показывает статус, фиксацию, количество спутников.",
|
||||
"repeater_cliHelpGpsOnOff": "Переключает состояние питания GPS.",
|
||||
"repeater_cliHelpGpsSync": "Синхронизирует время ноды с часами GPS.",
|
||||
"repeater_cliHelpGpsSetLoc": "Устанавливает позицию ноды по координатам GPS и сохраняет в настройки.",
|
||||
"repeater_cliHelpGpsAdvert": "Показывает конфигурацию передачи местоположения в анонсированиях:\n- none: не включать местоположение\n- share: передавать GPS-координаты (из SensorManager)\n- prefs: передавать координаты из настроек",
|
||||
"repeater_cliHelpGpsAdvertSet": "Устанавливает конфигурацию передачи местоположения.",
|
||||
"repeater_commandsListTitle": "Список команд",
|
||||
"repeater_commandsListNote": "ПРИМЕЧАНИЕ: для большинства команд «set ...» существуют соответствующие команды «get ...».",
|
||||
"repeater_general": "Общие",
|
||||
"repeater_settingsCategory": "Настройки",
|
||||
"repeater_bridge": "Мост",
|
||||
"repeater_logging": "Журналирование",
|
||||
"repeater_neighborsRepeaterOnly": "Соседи (только для репитеров)",
|
||||
"repeater_regionManagementRepeaterOnly": "Управление регионами (только для репитеров)",
|
||||
"repeater_regionNote": "Команды регионов введены для управления определениями регионов и правами доступа.",
|
||||
"repeater_gpsManagement": "Управление GPS",
|
||||
"repeater_gpsNote": "Команда gps введена для управления параметрами, связанными с местоположением.",
|
||||
"telemetry_receivedData": "Полученные телеметрические данные",
|
||||
"telemetry_requestTimeout": "Время ожидания телеметрии истекло.",
|
||||
"telemetry_errorLoading": "Ошибка загрузки телеметрии: {error}",
|
||||
"telemetry_noData": "Данные телеметрии недоступны.",
|
||||
"telemetry_channelTitle": "Канал {channel}",
|
||||
"telemetry_batteryLabel": "Батарея",
|
||||
"telemetry_voltageLabel": "Напряжение",
|
||||
"telemetry_mcuTemperatureLabel": "Температура МК",
|
||||
"telemetry_temperatureLabel": "Температура",
|
||||
"telemetry_currentLabel": "Ток",
|
||||
"telemetry_batteryValue": "{percent}% / {volts}В",
|
||||
"telemetry_voltageValue": "{volts}В",
|
||||
"telemetry_currentValue": "{amps}А",
|
||||
"telemetry_temperatureValue": "{celsius}°C / {fahrenheit}°F",
|
||||
"neighbors_receivedData": "Полученные данные о соседях",
|
||||
"neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.",
|
||||
"neighbors_errorLoading": "Ошибка загрузки соседей: {error}",
|
||||
"neighbors_repeatersNeighbours": "Соседи репитеров",
|
||||
"neighbors_noData": "Данные о соседях недоступны.",
|
||||
"neighbors_unknownContact": "Неизвестный {pubkey}",
|
||||
"neighbors_heardA ago": "Слышали: {time} назад",
|
||||
"channelPath_title": "Путь пакета",
|
||||
"channelPath_viewMap": "Посмотреть на карте",
|
||||
"channelPath_otherObservedPaths": "Другие наблюдаемые пути",
|
||||
"channelPath_repeaterHops": "Хопы через репитеры",
|
||||
"channelPath_noHopDetails": "Детали хопов для этого пакета не предоставлены.",
|
||||
"channelPath_messageDetails": "Детали сообщения",
|
||||
"channelPath_senderLabel": "Отправитель",
|
||||
"channelPath_timeLabel": "Время",
|
||||
"channelPath_repeatsLabel": "Повторы",
|
||||
"channelPath_pathLabel": "Путь {index}",
|
||||
"channelPath_observedLabel": "Наблюдаемый",
|
||||
"channelPath_observedPathTitle": "Наблюдаемый путь {index} • {hops}",
|
||||
"channelPath_noLocationData": "Нет данных о местоположении",
|
||||
"channelPath_timeWithDate": "{day}/{month} {time}",
|
||||
"channelPath_timeOnly": "{time}",
|
||||
"channelPath_unknownPath": "Неизвестный",
|
||||
"channelPath_floodPath": "Рассылка",
|
||||
"channelPath_directPath": "Прямой",
|
||||
"channelPath_observedZeroOf": "0 из {total} хопов",
|
||||
"channelPath_observedSomeOf": "{observed} из {total} хопов",
|
||||
"channelPath_mapTitle": "Карта пути",
|
||||
"channelPath_noRepeaterLocations": "Нет данных о местоположении репитеров для этого пути.",
|
||||
"channelPath_primaryPath": "Путь {index} (Основной)",
|
||||
"channelPath_pathLabelTitle": "Путь",
|
||||
"channelPath_observedPathHeader": "Наблюдаемый путь",
|
||||
"channelPath_selectedPathLabel": "{label} • {prefixes}",
|
||||
"channelPath_noHopDetailsAvailable": "Детали хопов для этого пакета недоступны.",
|
||||
"channelPath_unknownRepeater": "Неизвестный репитер",
|
||||
"community_title": "Сообщество",
|
||||
"community_create": "Создать сообщество",
|
||||
"community_createDesc": "Создать новое сообщество и поделиться через QR-код.",
|
||||
"community_join": "Присоединиться",
|
||||
"community_joinTitle": "Присоединиться к сообществу",
|
||||
"community_joinConfirmation": "Вы хотите присоединиться к сообществу \"{name}\"?",
|
||||
"community_scanQr": "Сканировать QR-код сообщества",
|
||||
"community_scanInstructions": "Наведите камеру на QR-код сообщества",
|
||||
"community_showQr": "Показать QR-код",
|
||||
"community_publicChannel": "Публичный канал сообщества",
|
||||
"community_hashtagChannel": "Хэштег-канал сообщества",
|
||||
"community_name": "Имя сообщества",
|
||||
"community_enterName": "Введите имя сообщества",
|
||||
"community_created": "Сообщество \"{name}\" создано",
|
||||
"community_joined": "Присоединились к сообществу \"{name}\"",
|
||||
"community_qrTitle": "Поделиться сообществом",
|
||||
"community_qrInstructions": "Отсканируйте этот QR-код, чтобы присоединиться к \"{name}\"",
|
||||
"community_hashtagPrivacyHint": "Хэштег-каналы сообщества доступны только его участникам",
|
||||
"community_invalidQrCode": "Недопустимый QR-код сообщества",
|
||||
"community_alreadyMember": "Уже участник",
|
||||
"community_alreadyMemberMessage": "Вы уже участник сообщества \"{name}\".",
|
||||
"community_addPublicChannel": "Добавить публичный канал сообщества",
|
||||
"community_addPublicChannelHint": "Автоматически добавить публичный канал для этого сообщества",
|
||||
"community_noCommunities": "Вы ещё не присоединились ни к одному сообществу",
|
||||
"community_scanOrCreate": "Отсканируйте QR-код или создайте сообщество, чтобы начать",
|
||||
"community_manageCommunities": "Управление сообществами",
|
||||
"community_delete": "Покинуть сообщество",
|
||||
"community_deleteConfirm": "Покинуть \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "Это также удалит {count} канал(ов) и их сообщения.",
|
||||
"community_deleted": "Покинули сообщество \"{name}\"",
|
||||
"community_regenerateSecret": "Пересоздать секрет",
|
||||
"community_regenerateSecretConfirm": "Пересоздать секретный ключ для \"{name}\"? Все участники должны будут отсканировать новый QR-код для продолжения общения.",
|
||||
"community_regenerate": "Пересоздать",
|
||||
"community_secretRegenerated": "Секрет пересоздан для \"{name}\"",
|
||||
"community_updateSecret": "Обновить секрет",
|
||||
"community_secretUpdated": "Секрет обновлён для \"{name}\"",
|
||||
"community_scanToUpdateSecret": "Отсканируйте новый QR-код, чтобы обновить секрет для \"{name}\"",
|
||||
"community_addHashtagChannel": "Добавить хэштег-канал сообщества",
|
||||
"community_addHashtagChannelDesc": "Добавить хэштег-канал для этого сообщества",
|
||||
"community_selectCommunity": "Выбрать сообщество",
|
||||
"community_regularHashtag": "Обычный хэштег",
|
||||
"community_regularHashtagDesc": "Публичный хэштег (любой может присоединиться)",
|
||||
"community_communityHashtag": "Хэштег сообщества",
|
||||
"community_communityHashtagDesc": "Доступен только участникам сообщества",
|
||||
"community_forCommunity": "Для {name}",
|
||||
"listFilter_tooltip": "Фильтр и сортировка",
|
||||
"listFilter_sortBy": "Сортировка по",
|
||||
"listFilter_latestMessages": "Последние сообщения",
|
||||
"listFilter_heardRecently": "Слышали недавно",
|
||||
"listFilter_az": "По алфавиту",
|
||||
"listFilter_filters": "Фильтры",
|
||||
"listFilter_all": "Все",
|
||||
"listFilter_users": "Пользователи",
|
||||
"listFilter_repeaters": "Репитеры",
|
||||
"listFilter_roomServers": "Серверы комнат",
|
||||
"listFilter_unreadOnly": "Только непрочитанные",
|
||||
"listFilter_newGroup": "Новая группа",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@neighbors_heardAgo": {
|
||||
"placeholders": {
|
||||
"time": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_open": "Открыть",
|
||||
"chat_couldNotOpenLink": "Не удалось открыть ссылку: {url}",
|
||||
"chat_openLink": "Открыть ссылку?",
|
||||
"chat_openLinkConfirmation": "Хотите открыть эту ссылку в вашем браузере?",
|
||||
"neighbors_heardAgo": "Слушал(а): {time} назад",
|
||||
"chat_invalidLink": "Неправильный формат ссылки",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Вы",
|
||||
"pathTrace_failed": "Путь трассировки не выполнен.",
|
||||
"pathTrace_notAvailable": "Трассировка пути недоступна.",
|
||||
"pathTrace_refreshTooltip": "Обновить Path Trace",
|
||||
"contacts_pathTrace": "Трассировка пути",
|
||||
"contacts_ping": "Пинговать",
|
||||
"contacts_repeaterPathTrace": "Отследить путь к ретранслятору",
|
||||
"contacts_repeaterPing": "Пинговать повторитель",
|
||||
"contacts_roomPathTrace": "Трассировка пути к серверу комнаты",
|
||||
"contacts_roomPing": "Пинговать сервер комнаты",
|
||||
"contacts_chatTraceRoute": "Трассировка маршрута",
|
||||
"contacts_pathTraceTo": "Показать маршрут к {name}"
|
||||
}
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Otvoriť odkaz?",
|
||||
"chat_openLinkConfirmation": "Chcete otvoriť tento odkaz v prehliadači?",
|
||||
"chat_open": "Otvoriť",
|
||||
"chat_couldNotOpenLink": "Nepodarilo sa otvoriť odkaz: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Neplatný formát odkazu",
|
||||
"map_title": "Mapa uzlov",
|
||||
"map_noNodesWithLocation": "Žiadne uzly s údajmi o polohe",
|
||||
"map_nodesNeedGps": "Uholníky musia zdieľať svoje GPS súradnice, aby sa zobrazili na mape.",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Tým sa tiež vymaže {count} kanál/kanálov a ich správy.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Opustená komunita \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Veľký hashtag (ktočokoľvek sa môže pridať)",
|
||||
"community_communityHashtag": "Komunitný Hashtag",
|
||||
"community_communityHashtagDesc": "Špecifické pre členov komunity",
|
||||
"community_forCommunity": "Pre {name}"
|
||||
"community_forCommunity": "Pre {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_secretRegenerated": "Záznam pre \"{name}\" bol regenerovaný tajne",
|
||||
"community_regenerateSecretConfirm": "Znovu vygenerovať tajný kľúč pre \"{name}\"? Všetci členovia budú musieť skanovať nový QR kód, aby mohli nadviazať komunikáciu.",
|
||||
"community_regenerate": "Znovu vygenerovať",
|
||||
"community_regenerateSecret": "Zobraziť nový tajný kód",
|
||||
"community_scanToUpdateSecret": "Skáňte nový QR kód na aktualizáciu tajného hesla pre \"{name}\"",
|
||||
"community_updateSecret": "Aktualizovať tajné heslo",
|
||||
"community_secretUpdated": "Zmena tajnej slova pre \"{name}\"",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Vy",
|
||||
"pathTrace_failed": "Sledovanie cesty zlyhalo.",
|
||||
"pathTrace_notAvailable": "Path trace nie je k dispozícii.",
|
||||
"pathTrace_refreshTooltip": "Obnoviť Path Trace.",
|
||||
"contacts_pathTrace": "Sledovanie lúčov",
|
||||
"contacts_ping": "Pingovať",
|
||||
"contacts_repeaterPathTrace": "Sledovanie cesty k opakovaču",
|
||||
"contacts_repeaterPing": "Pingovať opakovač",
|
||||
"contacts_roomPathTrace": "Sledovanie cesty k serveru miestnosti",
|
||||
"contacts_roomPing": "Ping server miestnosti",
|
||||
"contacts_chatTraceRoute": "Sledovať trasu lúča",
|
||||
"contacts_pathTraceTo": "Sledovať trasu k {name}"
|
||||
}
|
||||
|
||||
+208
-140
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"@@locale": "sl",
|
||||
"appTitle": "MeshCore Open",
|
||||
"nav_contacts": "Kontakti",
|
||||
"nav_contacts": "Stiki",
|
||||
"nav_channels": "Kanali",
|
||||
"nav_map": "Karta",
|
||||
"common_cancel": "Prekliči",
|
||||
@@ -69,49 +69,49 @@
|
||||
},
|
||||
"scanner_stop": "Prekliči",
|
||||
"scanner_scan": "Skeniraj",
|
||||
"device_quickSwitch": "Hitro preklopiti",
|
||||
"device_quickSwitch": "Hitro preklop",
|
||||
"device_meshcore": "MeshCore",
|
||||
"settings_title": "Nastavitve",
|
||||
"settings_deviceInfo": "Informacije o napravei",
|
||||
"settings_appSettings": "Nastavitve aplikacije",
|
||||
"settings_appSettingsSubtitle": "Obveščanja, sporoščanje in zemljevidi.",
|
||||
"settings_nodeSettings": "Nastavitve časa",
|
||||
"settings_nodeName": "Ime omrežno mesto",
|
||||
"settings_nodeNameNotSet": "Nezavedeno",
|
||||
"settings_nodeNameHint": "Vnesite ime časa",
|
||||
"settings_nodeSettings": "Nastavitev časa",
|
||||
"settings_nodeName": "Ime node-a",
|
||||
"settings_nodeNameNotSet": "Ni nastavljeno",
|
||||
"settings_nodeNameHint": "Vnesite ime node-a",
|
||||
"settings_nodeNameUpdated": "Ime posodobljeno",
|
||||
"settings_radioSettings": "Nastavitve radija",
|
||||
"settings_radioSettingsSubtitle": "Frekvenca, moč, razširni faktor",
|
||||
"settings_radioSettingsSubtitle": "Frekvenca, moč, razširitveni faktor",
|
||||
"settings_radioSettingsUpdated": "Radio nastavitve posodobljene",
|
||||
"settings_location": "Lokacija",
|
||||
"settings_locationSubtitle": "GPS koordinate",
|
||||
"settings_locationUpdated": "Lokacija posodobljena",
|
||||
"settings_locationBothRequired": "Vnesite širino in dolžino.",
|
||||
"settings_locationInvalid": "Neveljna zemeljska širina ali dolžina.",
|
||||
"settings_locationInvalid": "Neveljavna zemeljska širina ali dolžina.",
|
||||
"settings_latitude": "Širina",
|
||||
"settings_longitude": "Dolžina",
|
||||
"settings_privacyMode": "Mod podjetja",
|
||||
"settings_privacyMode": "Zasebnost",
|
||||
"settings_privacyModeSubtitle": "Skrita imena/lokacije v oglasih",
|
||||
"settings_privacyModeToggle": "Omogoči način zasebnosti, da skrijemo tvoje ime in lokacijo v oglasih.",
|
||||
"settings_privacyModeEnabled": "Privatni režim je omogočen.",
|
||||
"settings_privacyModeDisabled": "Privatni režim je onemogočen.",
|
||||
"settings_privacyModeEnabled": "Privatni način je omogočen.",
|
||||
"settings_privacyModeDisabled": "Privatni način je onemogočen.",
|
||||
"settings_actions": "Akcije",
|
||||
"settings_sendAdvertisement": "Pošlji Oglas",
|
||||
"settings_sendAdvertisementSubtitle": "Trenutna prisotnost v oddajah",
|
||||
"settings_advertisementSent": "Oglas poslan",
|
||||
"settings_syncTime": "Ugasniti čas",
|
||||
"settings_syncTimeSubtitle": "Nastavi uro naprave v čas telefona",
|
||||
"settings_timeSynchronized": "Sinhronizirano po času",
|
||||
"settings_syncTime": "Nastavi uro",
|
||||
"settings_syncTimeSubtitle": "Nastavi uro naprave na čas telefona",
|
||||
"settings_timeSynchronized": "Ura sinhronizirana",
|
||||
"settings_refreshContacts": "Ponovno obišči kontakte",
|
||||
"settings_refreshContactsSubtitle": "Ponovno naloži seznam kontaktov iz naprave",
|
||||
"settings_rebootDevice": "Restart Naprave",
|
||||
"settings_rebootDeviceSubtitle": "Ponovite zažetek naprave MeshCore",
|
||||
"settings_rebootDeviceConfirm": "Ste prepričani, da želite ponovno zagon napravke? Boste odvisni od omrežja.",
|
||||
"settings_debug": "Napravi popravek",
|
||||
"settings_bleDebugLog": "Logarjev zapis BLE",
|
||||
"settings_bleDebugLogSubtitle": "Navodila BLE, odgovori in surovo podatkovno",
|
||||
"settings_appDebugLog": "Log zapiske aplikacije",
|
||||
"settings_appDebugLogSubtitle": "Prijavni sporočila aplikacije",
|
||||
"settings_refreshContactsSubtitle": "Ponovno naloži seznam stikov v napravi",
|
||||
"settings_rebootDevice": "Ponovni zagon naprave",
|
||||
"settings_rebootDeviceSubtitle": "Ponovno zaženi MeshCore napravo",
|
||||
"settings_rebootDeviceConfirm": "Ste prepričani, da želite ponovno zagnati napravo? Povezava bo prekinjena.",
|
||||
"settings_debug": "Debug",
|
||||
"settings_bleDebugLog": "BLE debug log (razhroščevanje)",
|
||||
"settings_bleDebugLogSubtitle": "BLE ukazi, odgovori in surovi podatki",
|
||||
"settings_appDebugLog": "Logi aplikacije",
|
||||
"settings_appDebugLogSubtitle": "Debug sporočila aplikacije",
|
||||
"settings_about": "Oglejte si",
|
||||
"settings_aboutVersion": "MeshCore Open v{version}",
|
||||
"@settings_aboutVersion": {
|
||||
@@ -121,14 +121,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings_aboutLegalese": "MeshCore Odprtokodni Projekt 2024",
|
||||
"settings_aboutDescription": "Odprtokodni Flutter kličnik za naprave za LoRa mrežo MeshCore.",
|
||||
"settings_aboutLegalese": "Odprtokodni projekt MeshCore 2024",
|
||||
"settings_aboutDescription": "Odprtokodni Flutter klient za naprave za LoRa omrežje MeshCore.",
|
||||
"settings_infoName": "Ime",
|
||||
"settings_infoId": "ID",
|
||||
"settings_infoStatus": "Status",
|
||||
"settings_infoBattery": "Baterija",
|
||||
"settings_infoPublicKey": "Ključ javnega tipa",
|
||||
"settings_infoContactsCount": "Število kontaktov",
|
||||
"settings_infoPublicKey": "Javni ključ",
|
||||
"settings_infoContactsCount": "Število stikov",
|
||||
"settings_infoChannelCount": "Število kanalov",
|
||||
"settings_presets": "Prednastavitve",
|
||||
"settings_preset915Mhz": "915 MHz",
|
||||
@@ -136,15 +136,15 @@
|
||||
"settings_preset433Mhz": "433 MHz",
|
||||
"settings_frequency": "Frekvenca (MHz)",
|
||||
"settings_frequencyHelper": "300,00 - 2500,00",
|
||||
"settings_frequencyInvalid": "Neveljčna frekvenca (300-2500 MHz)",
|
||||
"settings_frequencyInvalid": "Neveljavna frekvenca (300-2500 MHz)",
|
||||
"settings_bandwidth": "Pasovna širina",
|
||||
"settings_spreadingFactor": "Razširitveni faktor",
|
||||
"settings_codingRate": "Programska hitrost",
|
||||
"settings_txPower": "TX Moč (dBm)",
|
||||
"settings_txPowerHelper": "0 - 22",
|
||||
"settings_txPowerInvalid": "Neveljaven TX moč (0-22 dBm)",
|
||||
"settings_longRange": "Dolenje območje",
|
||||
"settings_fastSpeed": "Hitra hitrost",
|
||||
"settings_txPowerInvalid": "Neveljavna TX moč (0-22 dBm)",
|
||||
"settings_longRange": "DDolg doseg",
|
||||
"settings_fastSpeed": "Visoka hitrost",
|
||||
"settings_error": "Napaka: {message}",
|
||||
"@settings_error": {
|
||||
"placeholders": {
|
||||
@@ -156,8 +156,8 @@
|
||||
"appSettings_title": "Nastavitve aplikacije",
|
||||
"appSettings_appearance": "Prikaži",
|
||||
"appSettings_theme": "Tema",
|
||||
"appSettings_themeSystem": "Predpomnilnik sistema",
|
||||
"appSettings_themeLight": "Luč",
|
||||
"appSettings_themeSystem": "Sistemska tema",
|
||||
"appSettings_themeLight": "Svetlo",
|
||||
"appSettings_themeDark": "Temno",
|
||||
"appSettings_language": "Jezik",
|
||||
"appSettings_languageSystem": "Sistemska privzeta vrednost",
|
||||
@@ -174,8 +174,8 @@
|
||||
"appSettings_languageNl": "Nederlands",
|
||||
"appSettings_languageSk": "Slovenčina",
|
||||
"appSettings_languageBg": "Български",
|
||||
"appSettings_notifications": "Obveščanja",
|
||||
"appSettings_enableNotifications": "Omogoči obveščanje",
|
||||
"appSettings_notifications": "Obvestila",
|
||||
"appSettings_enableNotifications": "Omogoči obvestila",
|
||||
"appSettings_enableNotificationsSubtitle": "Prejmite obvestila o sporočilih in oglasih",
|
||||
"appSettings_notificationPermissionDenied": "Odobritev obvestila zavrnjena",
|
||||
"appSettings_notificationsEnabled": "Obvestila omogočena",
|
||||
@@ -185,19 +185,19 @@
|
||||
"appSettings_channelMessageNotifications": "Obvestila o sporočilih kanala",
|
||||
"appSettings_channelMessageNotificationsSubtitle": "Pokaži obvestilo ob prejemanju sporočil kanala",
|
||||
"appSettings_advertisementNotifications": "Opozorila o oglasih",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Pokaži obvestilo, ko so novi vozlišči odkrivljeni.",
|
||||
"appSettings_advertisementNotificationsSubtitle": "Pokaži obvestilo, ko so najdene nove naprave.",
|
||||
"appSettings_messaging": "Komuniciranje",
|
||||
"appSettings_clearPathOnMaxRetry": "Ponovite pot do cilja na največjem štetju",
|
||||
"appSettings_clearPathOnMaxRetrySubtitle": "Ponovi pot zimske obveščevalne poti po 5 neuspešnih poskusih pošiljanja",
|
||||
"appSettings_pathsWillBeCleared": "Potnice bodo očiščene po 5 neuspešnih poskusih.",
|
||||
"appSettings_pathsWillNotBeCleared": "Potniški poti ne bodo samodejno čiščeni.",
|
||||
"appSettings_autoRouteRotation": "Avtomatsko Občutke in Rotacije",
|
||||
"appSettings_autoRouteRotationSubtitle": "Med spreminjanjem med najboljšimi potmi in plovilnim načinom",
|
||||
"appSettings_pathsWillBeCleared": "Počisti pot po 5 neuspešnih poskusih.",
|
||||
"appSettings_pathsWillNotBeCleared": "Poti ne bodo samodejno čiščene.",
|
||||
"appSettings_autoRouteRotation": "Avtomatsko rotacija prenosne poti",
|
||||
"appSettings_autoRouteRotationSubtitle": "Menjaj med boljšo potjo in flood načinom",
|
||||
"appSettings_autoRouteRotationEnabled": "Samodejno krmilno rotiranje omogočeno",
|
||||
"appSettings_autoRouteRotationDisabled": "Samodejno krmilno rotiranje je onemogočeno",
|
||||
"appSettings_battery": "Baterija",
|
||||
"appSettings_batteryChemistry": "Razem z možnostmi",
|
||||
"appSettings_batteryChemistryPerDevice": "Nastavitve za naprave ({deviceName})",
|
||||
"appSettings_batteryChemistry": "Kemija baterije",
|
||||
"appSettings_batteryChemistryPerDevice": "Nastavitev za napravo ({deviceName})",
|
||||
"@appSettings_batteryChemistryPerDevice": {
|
||||
"placeholders": {
|
||||
"deviceName": {
|
||||
@@ -205,20 +205,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_batteryChemistryConnectFirst": "Povežite se z napravo za izbiro",
|
||||
"appSettings_batteryChemistryConnectFirst": "Za izbiro se poveži z napravo",
|
||||
"appSettings_batteryNmc": "18650 NMC (3,0-4,2V)",
|
||||
"appSettings_batteryLifepo4": "LiFePO4 (2,6–3,65 V)",
|
||||
"appSettings_batteryLipo": "LiPo (3,0-4,2V)",
|
||||
"appSettings_mapDisplay": "Prikaz zemljevide",
|
||||
"appSettings_showRepeaters": "Prikaži ponovitve",
|
||||
"appSettings_showRepeatersSubtitle": "Prikaži ponovljalne notranjosti na zemljeploscu",
|
||||
"appSettings_showChatNodes": "Prikaži čakalne notranjosti",
|
||||
"appSettings_showChatNodesSubtitle": "Prikaži pogovorni pike na zemljeploscu",
|
||||
"appSettings_showOtherNodes": "Pokaži druge vozlišča",
|
||||
"appSettings_showOtherNodesSubtitle": "Pokaži druge vrste notranjih elementov na zemljevalu.",
|
||||
"appSettings_timeFilter": "Filtri po času",
|
||||
"appSettings_timeFilterShowAll": "Pokaži vse notranje elemente",
|
||||
"appSettings_timeFilterShowLast": "Pokaži notranjosti iz zadnjih {hours} ur",
|
||||
"appSettings_mapDisplay": "Prikaz zemljevida",
|
||||
"appSettings_showRepeaters": "Prikaži repetitorje",
|
||||
"appSettings_showRepeatersSubtitle": "Prikaži repetitorje na mapi",
|
||||
"appSettings_showChatNodes": "Prikaži naprave za klepet",
|
||||
"appSettings_showChatNodesSubtitle": "Prikaži naprave na zemljevidu",
|
||||
"appSettings_showOtherNodes": "Pokaži druge naprave",
|
||||
"appSettings_showOtherNodesSubtitle": "Pokaži druge vrste naprav na zemljevidu.",
|
||||
"appSettings_timeFilter": "Filter po času",
|
||||
"appSettings_timeFilterShowAll": "Pokaži vse naprave",
|
||||
"appSettings_timeFilterShowLast": "Pokaži naprave v zadnjih {hours} urah",
|
||||
"@appSettings_timeFilterShowLast": {
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
@@ -226,15 +226,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_mapTimeFilter": "Filtri časa zemljevida",
|
||||
"appSettings_showNodesDiscoveredWithin": "Pokaži notranje čepke, odkrivene v:",
|
||||
"appSettings_allTime": "Vse čase",
|
||||
"appSettings_lastHour": "Minuto nazaj",
|
||||
"appSettings_mapTimeFilter": "Filter časa na zemljevidu",
|
||||
"appSettings_showNodesDiscoveredWithin": "Pokaži naprave odkrite v:",
|
||||
"appSettings_allTime": "Brez omejitev",
|
||||
"appSettings_lastHour": "V zadnji uri",
|
||||
"appSettings_last6Hours": "Zadnjih 6 ur",
|
||||
"appSettings_last24Hours": "Zadnjih 24 ur",
|
||||
"appSettings_lastWeek": "Lepošno",
|
||||
"appSettings_offlineMapCache": "Omrezni Poudni Arhiv",
|
||||
"appSettings_noAreaSelected": "Nizkana označena površina",
|
||||
"appSettings_lastWeek": "Prejšnji teden",
|
||||
"appSettings_offlineMapCache": "Shramba zemljevidov brez povezave",
|
||||
"appSettings_noAreaSelected": "Območje ni izbrano",
|
||||
"appSettings_areaSelectedZoom": "Izbrano območje (povečava {minZoom}-{maxZoom})",
|
||||
"@appSettings_areaSelectedZoom": {
|
||||
"placeholders": {
|
||||
@@ -246,19 +246,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"appSettings_debugCard": "Napravi popravek",
|
||||
"appSettings_appDebugLogging": "Programski Log",
|
||||
"appSettings_appDebugLoggingSubtitle": "Log aplikacijske debug sporočila za odpravljanje težav",
|
||||
"appSettings_appDebugLoggingEnabled": "Omogočeno zaznamovanje napak v aplikaciji",
|
||||
"appSettings_appDebugLoggingDisabled": "Programski logi aplikacije so onemogočeni.",
|
||||
"contacts_title": "Kontakti",
|
||||
"contacts_noContacts": "Še ni kontaktov.",
|
||||
"contacts_contactsWillAppear": "Kontakti se bodo prikazali, ko naprave oglasijo.",
|
||||
"contacts_searchContacts": "Iskanje kontaktov...",
|
||||
"contacts_noUnreadContacts": "Nerešeno kontaktov.",
|
||||
"contacts_noContactsFound": "Niti ena oseba ali skupine ni najdena.",
|
||||
"contacts_deleteContact": "Izbrisati Kontakt",
|
||||
"contacts_removeConfirm": "Izbrisati {contactName} iz kontaktov?",
|
||||
"appSettings_debugCard": "Razhroščevanje",
|
||||
"appSettings_appDebugLogging": "Programski dnevnik",
|
||||
"appSettings_appDebugLoggingSubtitle": "Dnevnik debug sporočil za odpravljanje težav",
|
||||
"appSettings_appDebugLoggingEnabled": "Beleženje napak v aplikaciji omogočeno",
|
||||
"appSettings_appDebugLoggingDisabled": "Beleženje napak v aplikacije onemogočeno.",
|
||||
"contacts_title": "Stiki",
|
||||
"contacts_noContacts": "Ni stikov.",
|
||||
"contacts_contactsWillAppear": "Stiki se bodo prikazali, ko se naprave oglasijo.",
|
||||
"contacts_searchContacts": "Iskanje stikov...",
|
||||
"contacts_noUnreadContacts": "Ne prebrani stiki.",
|
||||
"contacts_noContactsFound": "Stiki niso najdeni.",
|
||||
"contacts_deleteContact": "Izbriši stik",
|
||||
"contacts_removeConfirm": "Izbrišem {contactName} iz stikov?",
|
||||
"@contacts_removeConfirm": {
|
||||
"placeholders": {
|
||||
"contactName": {
|
||||
@@ -266,12 +266,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_manageRepeater": "Upravljajte Ponovitve",
|
||||
"contacts_roomLogin": "Vnos v sobo",
|
||||
"contacts_openChat": "Odprta kleta",
|
||||
"contacts_editGroup": "Uredi Skupino",
|
||||
"contacts_deleteGroup": "Izbrisati Skupino",
|
||||
"contacts_deleteGroupConfirm": "Odpovedati {groupName}?",
|
||||
"contacts_manageRepeater": "Upravljaj Ponovitve",
|
||||
"contacts_roomLogin": "Prijava v sobo",
|
||||
"contacts_openChat": "Odpri klepet",
|
||||
"contacts_editGroup": "Uredi skupino",
|
||||
"contacts_deleteGroup": "Izbriši skupino",
|
||||
"contacts_deleteGroupConfirm": "Izbriši {groupName}?",
|
||||
"@contacts_deleteGroupConfirm": {
|
||||
"placeholders": {
|
||||
"groupName": {
|
||||
@@ -279,8 +279,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_newGroup": "Novo skupino",
|
||||
"contacts_groupName": "Skupina imena",
|
||||
"contacts_newGroup": "Nova skupina",
|
||||
"contacts_groupName": "Ime skupine",
|
||||
"contacts_groupNameRequired": "Ime skupine je obvezno.",
|
||||
"contacts_groupAlreadyExists": "Skupina \"{name}\" že obstaja",
|
||||
"@contacts_groupAlreadyExists": {
|
||||
@@ -290,11 +290,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_filterContacts": "Filtri kontakt\\,...",
|
||||
"contacts_noContactsMatchFilter": "Niti ena oseba ne ustreza vašemu kriteriju.",
|
||||
"contacts_noMembers": "Nič članov.",
|
||||
"contacts_lastSeenNow": "Datum zadnjega vpisa zdaj",
|
||||
"contacts_lastSeenMinsAgo": "Zadnjič videti {minutes} minut nazaj",
|
||||
"contacts_filterContacts": "Filtriraj stik\\,...",
|
||||
"contacts_noContactsMatchFilter": "Noben stik ne ustreza vašemu kriteriju.",
|
||||
"contacts_noMembers": "Ni članov.",
|
||||
"contacts_lastSeenNow": "Nazadnje viden zdaj",
|
||||
"contacts_lastSeenMinsAgo": "Zadnjič viden pred {minutes} minutami",
|
||||
"@contacts_lastSeenMinsAgo": {
|
||||
"placeholders": {
|
||||
"minutes": {
|
||||
@@ -302,8 +302,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_lastSeenHourAgo": "Zadnjič ogledan pred 1 uro.",
|
||||
"contacts_lastSeenHoursAgo": "Zadnjič videti {hours} ur nazaj",
|
||||
"contacts_lastSeenHourAgo": "Zadnjič viden pred 1 uro.",
|
||||
"contacts_lastSeenHoursAgo": "Zadnjič viden pred {hours} urami",
|
||||
"@contacts_lastSeenHoursAgo": {
|
||||
"placeholders": {
|
||||
"hours": {
|
||||
@@ -311,8 +311,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"contacts_lastSeenDayAgo": "Zadnjič ogledan pred 1 dnem",
|
||||
"contacts_lastSeenDaysAgo": "Zadnjič videti {days} dni nazaj",
|
||||
"contacts_lastSeenDayAgo": "Zadnjič viden pred 1 dnem",
|
||||
"contacts_lastSeenDaysAgo": "Zadnjič viden pred {days} dnem",
|
||||
"@contacts_lastSeenDaysAgo": {
|
||||
"placeholders": {
|
||||
"days": {
|
||||
@@ -321,10 +321,10 @@
|
||||
}
|
||||
},
|
||||
"channels_title": "Kanali",
|
||||
"channels_noChannelsConfigured": "Nekonfigurirane kanale",
|
||||
"channels_addPublicChannel": "Dodaj Objavni Kanal",
|
||||
"channels_noChannelsConfigured": "Kanali še niso konfigurirani",
|
||||
"channels_addPublicChannel": "Dodaj javni kanal",
|
||||
"channels_searchChannels": "Poišči kanale...",
|
||||
"channels_noChannelsFound": "Niti kanalov najti ni.",
|
||||
"channels_noChannelsFound": "Ne najdem kanalov.",
|
||||
"channels_channelIndex": "Kanal {index}",
|
||||
"@channels_channelIndex": {
|
||||
"placeholders": {
|
||||
@@ -334,13 +334,13 @@
|
||||
}
|
||||
},
|
||||
"channels_hashtagChannel": "Hashtag kanal",
|
||||
"channels_public": "javno",
|
||||
"channels_private": "Zasebno",
|
||||
"channels_publicChannel": "Ogljišna skupina",
|
||||
"channels_privateChannel": "Zatemniščen kanal",
|
||||
"channels_public": "Javni",
|
||||
"channels_private": "Zasebni",
|
||||
"channels_publicChannel": "Javni kanal",
|
||||
"channels_privateChannel": "Zasebni kanal",
|
||||
"channels_editChannel": "Uredi kanal",
|
||||
"channels_deleteChannel": "Pošlji kanal",
|
||||
"channels_deleteChannelConfirm": "Izbrisati \"{name}\"? To se ne da povrniti.",
|
||||
"channels_deleteChannelConfirm": "Izbrišem \"{name}\"? To se ne da povrniti.",
|
||||
"@channels_deleteChannelConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
@@ -424,8 +424,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_typeMessage": "Vnesite sporočilo...",
|
||||
"chat_messageTooLong": "Pošiljanje sporočila je onemogočeno, saj je preveliko (maksimalno {maxBytes} bajt).",
|
||||
"chat_typeMessage": "Vnesi sporočilo...",
|
||||
"chat_messageTooLong": "Pošiljanje sporočila je onemogočeno, saj je preveliko (maksimalno {maxBytes} byte-ov).",
|
||||
"@chat_messageTooLong": {
|
||||
"placeholders": {
|
||||
"maxBytes": {
|
||||
@@ -433,9 +433,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_messageCopied": "Pošljeno sporočilo",
|
||||
"chat_messageDeleted": "Pošiljanje sporočila izbrisano",
|
||||
"chat_retryingMessage": "Ponovna poskus.",
|
||||
"chat_messageCopied": "Sporočilo poslano",
|
||||
"chat_messageDeleted": "Sporočilo izbrisano",
|
||||
"chat_retryingMessage": "Ponovni poskus.",
|
||||
"chat_retryCount": "Ponovit {current}/{max}",
|
||||
"@chat_retryCount": {
|
||||
"placeholders": {
|
||||
@@ -448,31 +448,31 @@
|
||||
}
|
||||
},
|
||||
"chat_sendGif": "Pošlji GIF",
|
||||
"chat_reply": "Odpošlji",
|
||||
"chat_addReaction": "Dodaj Reakcijo",
|
||||
"chat_reply": "Odgovori",
|
||||
"chat_addReaction": "Dodaj reakcijo",
|
||||
"chat_me": "jaz",
|
||||
"emojiCategorySmileys": "Emoji",
|
||||
"emojiCategoryGestures": "Gestikulacije",
|
||||
"emojiCategoryHearts": "Srce",
|
||||
"emojiCategoryObjects": "Predmeti",
|
||||
"gifPicker_title": "Izberi GIF",
|
||||
"gifPicker_searchHint": "Iskalite GIF-e...",
|
||||
"gifPicker_poweredBy": "Naprodno z GIPHY",
|
||||
"gifPicker_noGifsFound": "Niti GIF-jev najti ni.",
|
||||
"gifPicker_failedLoad": "Neuspešno je naložilo GIF-e",
|
||||
"gifPicker_failedSearch": "Posodobit neuspešno.",
|
||||
"gifPicker_searchHint": "Išči GIF-e...",
|
||||
"gifPicker_poweredBy": "Napredno z GIPHY",
|
||||
"gifPicker_noGifsFound": "Ne najdem GIF-ov.",
|
||||
"gifPicker_failedLoad": "Neuspešno nalaganje GIF-a",
|
||||
"gifPicker_failedSearch": "Iskanje neuspešno.",
|
||||
"gifPicker_noInternet": "Ni internetne povezave",
|
||||
"debugLog_appTitle": "Log zapiske aplikacije",
|
||||
"debugLog_bleTitle": "Logarjev zapis BLE",
|
||||
"debugLog_copyLog": "Kopiraj zapiske",
|
||||
"debugLog_clearLog": "Pasters log",
|
||||
"debugLog_copied": "Kopirana belež poteka.",
|
||||
"debugLog_bleCopied": "Kopirana beležke iz BLE",
|
||||
"debugLog_noEntries": "Še ni ustvarjenih debug zapisov.",
|
||||
"debugLog_enableInSettings": "Omogoči beleženje napak v aplikaciji v nastavitvah",
|
||||
"debugLog_frames": "Okna",
|
||||
"debugLog_bleTitle": "Log zapis BLE",
|
||||
"debugLog_copyLog": "Kopiraj dnevnik",
|
||||
"debugLog_clearLog": "Briši log",
|
||||
"debugLog_copied": "Beležka kopirana.",
|
||||
"debugLog_bleCopied": "Kopirana beležka iz BLE",
|
||||
"debugLog_noEntries": "Ni ustvarjenih debug zapisov.",
|
||||
"debugLog_enableInSettings": "Omogoči beleženje napak v nastavitvah aplikacije",
|
||||
"debugLog_frames": "Okvirji",
|
||||
"debugLog_rawLogRx": "Svež Log-RX",
|
||||
"debugLog_noBleActivity": "Šele začnite z aktivnostjo BLE.",
|
||||
"debugLog_noBleActivity": "Ni BLE aktivnosti.",
|
||||
"debugFrame_length": "Izhodni rob: {count} bajtov",
|
||||
"@debugFrame_length": {
|
||||
"placeholders": {
|
||||
@@ -542,8 +542,8 @@
|
||||
"chat_forceFloodMode": "Nasilje obvezati v način",
|
||||
"chat_recentAckPaths": "Nedavni poti ACK (tap za uporabo):",
|
||||
"chat_pathHistoryFull": "Zapiske o poti so popolni. Izbriši vnose, da dodaš nove.",
|
||||
"chat_hopSingular": "skoč",
|
||||
"chat_hopPlural": "škrabec",
|
||||
"chat_hopSingular": "skok",
|
||||
"chat_hopPlural": "skokov",
|
||||
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
|
||||
"@chat_hopsCount": {
|
||||
"placeholders": {
|
||||
@@ -554,16 +554,16 @@
|
||||
},
|
||||
"chat_successes": "Uspešni",
|
||||
"chat_removePath": "Izbriši pot",
|
||||
"chat_noPathHistoryYet": "Še ni shranjenih poti.\nPošlji sporočilo za odkrivanje poti.",
|
||||
"chat_noPathHistoryYet": "Ni shranjenih poti.\nPošlji sporočilo za odkrivanje poti.",
|
||||
"chat_pathActions": "Potni ukazi:",
|
||||
"chat_setCustomPath": "Nastavi Prilozeno Pot",
|
||||
"chat_setCustomPathSubtitle": "Ročno določite potniško pot.",
|
||||
"chat_clearPath": "Čista pot",
|
||||
"chat_clearPath": "Počisti pot",
|
||||
"chat_clearPathSubtitle": "Ob naslednji pošiljanju znova zbrati.",
|
||||
"chat_pathCleared": "Pot je očiščena. Naslednje sporočilo bo ponovno odkril pot.",
|
||||
"chat_floodModeSubtitle": "Uporabi tipko usmerjevanja v meniju aplikacije.",
|
||||
"chat_floodModeEnabled": "Narejena je bila omrežna modaliteta. Vklopi jo znova preko ikone v meniju aplikacije.",
|
||||
"chat_fullPath": "Polni pot",
|
||||
"chat_fullPath": "Polna pot",
|
||||
"chat_pathDetailsNotAvailable": "Podrobnosti poti zaenkrat niso na voljo. Poskusite poslati sporočilo za osvežitev.",
|
||||
"chat_pathSetHops": "Pot nastavljen: {hopCount} {hopCount, plural, =1{hop} other{hops}} - {status}",
|
||||
"@chat_pathSetHops": {
|
||||
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Odpreti povezavo?",
|
||||
"chat_openLinkConfirmation": "Ali želite odpreti to povezavo v brskalniku?",
|
||||
"chat_open": "Odpri",
|
||||
"chat_couldNotOpenLink": "Povezave ni bilo mogoče odpreti: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Neveljavna oblika povezave",
|
||||
"map_title": "Mapa omrežja",
|
||||
"map_noNodesWithLocation": "Nihče od notranjih elementov nima podatkov o lokaciji.",
|
||||
"map_nodesNeedGps": "Omrežje morajo deliti svoje GPS koordinate,\nda se prikazao na zemljeobrazniku.",
|
||||
@@ -1092,13 +1104,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"repeater_cliQuickGetName": "Dobiti ime",
|
||||
"repeater_cliQuickGetName": "Pridobi ime",
|
||||
"repeater_cliQuickGetRadio": "Dobiti Radiopravo",
|
||||
"repeater_cliQuickGetTx": "Dobiti TX",
|
||||
"repeater_cliQuickGetTx": "Pridobi TX",
|
||||
"repeater_cliQuickNeighbors": "Sosedi",
|
||||
"repeater_cliQuickVersion": "Različica",
|
||||
"repeater_cliQuickAdvertise": "Oglasite",
|
||||
"repeater_cliQuickClock": "Urnik",
|
||||
"repeater_cliQuickClock": "Ura",
|
||||
"repeater_cliHelpAdvert": "Pošlje paket oglasov",
|
||||
"repeater_cliHelpReboot": "Ponastavi naprave. (Opomba, lahko pride do 'Timeouta', kar je normalno)",
|
||||
"repeater_cliHelpClock": "Prikaže trenutno uro po uri naprave.",
|
||||
@@ -1130,7 +1142,7 @@
|
||||
"repeater_cliHelpSetBridgeSecret": "Nastavi skrivni dostop za mostove ESPNOW.",
|
||||
"repeater_cliHelpSetAdcMultiplier": "Nastavi prilagoditev faktorja za prilagoditev poravnalnega napetosti baterije (podprt le na izbranih ploščah).",
|
||||
"repeater_cliHelpTempRadio": "Nastavi začasne radio parametre za določeno časovno obdobje, kar po preteku časa vrne originalne radio parametre. (ne shranjuje v preferencije).",
|
||||
"repeater_cliHelpSetPerm": "Modificira ACL. Odstrani ustreznu vnos (po predponi pubkeyja), če je \"permissions\" enako nič. Dodaja nov vnos, če je pubkey-hex v celoti in trenutno ni v ACL. Posodobi vnos po ustreznem predponi pubkeyja. Bitje dovoljenj se razlikuje glede na firmware vlogo, vendar so prvi dve bitki: 0 (Gost), 1 (Lezenje samo), 2 (Lezenje in pisanje), 3 (Administrator).",
|
||||
"repeater_cliHelpSetPerm": "Modificira ACL. Odstrani ustrezen vnos (po predponi pubkeyja), če je \"permissions\" enako nič. Dodaja nov vnos, če je pubkey-hex v celoti in trenutno ni v ACL. Posodobi vnos po ustreznem predponi pubkeyja. Bitje dovoljenj se razlikuje glede na firmware vlogo, vendar so prvi dve bitki: 0 (Gost), 1 (Lezenje samo), 2 (Lezenje in pisanje), 3 (Administrator).",
|
||||
"repeater_cliHelpGetBridgeType": "Dobrodošli pri izbiri vrste mostu: brez, rs232, espnow",
|
||||
"repeater_cliHelpLogStart": "Začnete beleženje paketov v datotekovni sistem.",
|
||||
"repeater_cliHelpLogStop": "Ustavite beleženje paketov v datotečno sistem.",
|
||||
@@ -1159,8 +1171,8 @@
|
||||
"repeater_settingsCategory": "Nastavitve",
|
||||
"repeater_bridge": "Most",
|
||||
"repeater_logging": "Logiranje",
|
||||
"repeater_neighborsRepeaterOnly": "Sosedi (le za ponovitelja)",
|
||||
"repeater_regionManagementRepeaterOnly": "Upravljanje regij (zgolj za ponovitve)",
|
||||
"repeater_neighborsRepeaterOnly": "Sosedi (le za repetitorje)",
|
||||
"repeater_regionManagementRepeaterOnly": "Upravljanje regij (zgolj za repetitorje)",
|
||||
"repeater_regionNote": "Regionske ukazi so bili uvedeni za upravljanje z regijskimi definicijami in dovolili.",
|
||||
"repeater_gpsManagement": "Upravljanje GPS",
|
||||
"repeater_gpsNote": "GPS ukaz je bil uveden za upravljanje z vprašanji, povezanimi z lokacijo.",
|
||||
@@ -1232,9 +1244,9 @@
|
||||
"channelPath_repeaterHops": "Skoki ponovitelja",
|
||||
"channelPath_noHopDetails": "Podrobnosti o paketu za dostavo niso navedene.",
|
||||
"channelPath_messageDetails": "Podrobnosti sporočila",
|
||||
"channelPath_senderLabel": "Pošiljalec",
|
||||
"channelPath_timeLabel": "Čas",
|
||||
"channelPath_repeatsLabel": "Ponovi",
|
||||
"channelPath_senderLabel": "Pošiljatelj",
|
||||
"channelPath_timeLabel": "Ura",
|
||||
"channelPath_repeatsLabel": "Ponovitve",
|
||||
"channelPath_pathLabel": "Pot {index}",
|
||||
"channelPath_observedLabel": "Opazovani",
|
||||
"channelPath_observedPathTitle": "Opazovana pot {index} • {hops}",
|
||||
@@ -1466,23 +1478,79 @@
|
||||
"community_addPublicChannel": "Dodaj Objavni Kanal Komunitarja",
|
||||
"community_addPublicChannelHint": "Samodejno dodaj javni kanal za to skupnost.",
|
||||
"community_noCommunities": "Še nobena skupnost se ni pridružila.",
|
||||
"community_scanOrCreate": "Skenirajte QR kodo ali ustvarite skupnost za začetek.",
|
||||
"community_manageCommunities": "Upravljajte skupnosti",
|
||||
"community_scanOrCreate": "Skeniraj QR kodo ali ustvari skupnost za začetek.",
|
||||
"community_manageCommunities": "Upravljanje skupnosti",
|
||||
"community_delete": "Opusti skupnost",
|
||||
"community_deleteConfirm": "Zapustiti \"{name}\"?",
|
||||
"community_deleteConfirm": "Zapusti \"{name}\"?",
|
||||
"community_deleteChannelsWarning": "To bo izbrisalo tudi {count} kanal/kanalov in njihova sporočila.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Zapustil skupnost \"{name}\"",
|
||||
"community_addHashtagChannel": "Dodaj Oznako Obštnine",
|
||||
"community_addHashtagChannel": "Dodaj hashtag kanal",
|
||||
"community_addHashtagChannelDesc": "Dodajte hashtag kanal za to skupnost.",
|
||||
"community_selectCommunity": "Izberi skupnost",
|
||||
"community_regularHashtag": "Oznaka s hashtagom",
|
||||
"community_regularHashtagDesc": "javna oznaka (kateri koli lahko sodelujejo)",
|
||||
"community_regularHashtagDesc": "javna oznaka (kdorkoli lahko sodeluje)",
|
||||
"community_communityHashtag": "Skupnostni hashtag",
|
||||
"community_communityHashtagDesc": "Izključeno za uporabnike skupnosti",
|
||||
"community_forCommunity": "Za {name}"
|
||||
"community_forCommunity": "Za {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_secretRegenerated": "Geslo za \"{name}\" ponovno ustvarjeno",
|
||||
"community_regenerateSecret": "Ponovno ustvari geslo",
|
||||
"community_regenerateSecretConfirm": "Preberite novo tajno geslo za \"{name}\"? Vsi članici morajo prebrati novo QR kodo, da lahko nadaljujejo s komunikacijo.",
|
||||
"community_regenerate": "Preberi znova",
|
||||
"community_scanToUpdateSecret": "Skeniraj novo QR kodo za posodabljanje ključa za {name}",
|
||||
"community_updateSecret": "Ažuriraj ključ",
|
||||
"community_secretUpdated": "Skrivnostno spremembo za \"{name}\"",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Ti",
|
||||
"pathTrace_failed": "Sledenje poti ni uspelo.",
|
||||
"pathTrace_notAvailable": "Potni sled ni na voljo.",
|
||||
"pathTrace_refreshTooltip": "Osveži Path Trace.",
|
||||
"contacts_pathTrace": "Sledenje poti",
|
||||
"contacts_ping": "Pingati",
|
||||
"contacts_repeaterPathTrace": "Sledi poti do ponavljalnika",
|
||||
"contacts_repeaterPing": "Pinguj ponavljalnik",
|
||||
"contacts_roomPathTrace": "Sledenje poti do strežnika sobe",
|
||||
"contacts_roomPing": "Ping strežnik sobe",
|
||||
"contacts_chatTraceRoute": "Slediti poti žarkov",
|
||||
"contacts_pathTraceTo": "Trace route to {name}"
|
||||
}
|
||||
|
||||
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "Öppna länk?",
|
||||
"chat_openLinkConfirmation": "Vill du öppna den här länken i din webbläsare?",
|
||||
"chat_open": "Öppna",
|
||||
"chat_couldNotOpenLink": "Kunde inte öppna länken: {url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "Ogiltigt länkformat",
|
||||
"map_title": "Nodkarta",
|
||||
"map_noNodesWithLocation": "Inga noder med platsinformation",
|
||||
"map_nodesNeedGps": "Noder måste dela sina GPS-koordinater\nför att visas på kartan",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "Detta kommer också att radera {count} kanal/kanaler och deras meddelanden.",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "Lämnade community \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "Offentlig hashtag (alla kan gå med)",
|
||||
"community_communityHashtagDesc": "Endast för medlemmar",
|
||||
"community_forCommunity": "För {name}",
|
||||
"community_communityHashtag": "Community Hashtag"
|
||||
"community_communityHashtag": "Community Hashtag",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerate": "Regenerera",
|
||||
"community_regenerateSecretConfirm": "Regenerera den hemliga nyckeln för \"{name}\"? Alla medlemmar måste scanna den nya QR-koden för att fortsätta kommunicera.",
|
||||
"community_secretRegenerated": "Lösenord återskapad för \"{name}\"",
|
||||
"community_regenerateSecret": "Regenerera hemlig kod",
|
||||
"community_scanToUpdateSecret": "Skanna den nya QR-koden för att uppdatera hemligheten för \"{name}\"",
|
||||
"community_secretUpdated": "Hemlighet uppdaterad för \"{name}\"",
|
||||
"community_updateSecret": "Uppdatera hemlighet",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "Du",
|
||||
"pathTrace_failed": "Sökvägsföljning misslyckades.",
|
||||
"pathTrace_notAvailable": "Path trace ej tillgänglig.",
|
||||
"pathTrace_refreshTooltip": "Uppdatera Path Trace",
|
||||
"contacts_pathTrace": "Path Trace",
|
||||
"contacts_ping": "Ping",
|
||||
"contacts_repeaterPathTrace": "Vägspårning till repeater",
|
||||
"contacts_repeaterPing": "Ping-repeater",
|
||||
"contacts_roomPathTrace": "Vägspårning till rumserver",
|
||||
"contacts_roomPing": "Ping rumsserver",
|
||||
"contacts_chatTraceRoute": "Spåra rutt",
|
||||
"contacts_pathTraceTo": "Spåra rutt till {name}"
|
||||
}
|
||||
|
||||
+1557
File diff suppressed because it is too large
Load Diff
+70
-2
@@ -604,6 +604,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_openLink": "打开链接?",
|
||||
"chat_openLinkConfirmation": "您想在浏览器中打开此链接吗?",
|
||||
"chat_open": "打开",
|
||||
"chat_couldNotOpenLink": "无法打开链接:{url}",
|
||||
"@chat_couldNotOpenLink": {
|
||||
"placeholders": {
|
||||
"url": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"chat_invalidLink": "链接格式无效",
|
||||
"map_title": "节点地图",
|
||||
"map_noNodesWithLocation": "没有具有位置数据的节点",
|
||||
"map_nodesNeedGps": "节点需要共享它们的 GPS 坐标\n才能在地图上显示",
|
||||
@@ -1473,7 +1485,9 @@
|
||||
"community_deleteChannelsWarning": "这也将删除 {count} 个频道及其消息。",
|
||||
"@community_deleteChannelsWarning": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_deleted": "已退出社区 \"{name}\"",
|
||||
@@ -1484,5 +1498,59 @@
|
||||
"community_regularHashtagDesc": "公共话题(任何人都可以加入)",
|
||||
"community_communityHashtag": "社区标签",
|
||||
"community_communityHashtagDesc": "仅限社区成员使用",
|
||||
"community_forCommunity": "对于 {name}"
|
||||
"community_forCommunity": "对于 {name}",
|
||||
"@community_regenerateSecretConfirm": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretRegenerated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_secretUpdated": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"@community_scanToUpdateSecret": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"community_regenerateSecret": "重新生成密钥",
|
||||
"community_secretRegenerated": "密码已重置为“{name}”",
|
||||
"community_regenerate": "重新生成",
|
||||
"community_regenerateSecretConfirm": "重新生成“{name}”的秘密密钥?所有成员将需要扫描新的二维码才能继续沟通。",
|
||||
"community_scanToUpdateSecret": "扫描新的二维码更新\"{name}\"的密码",
|
||||
"community_updateSecret": "更新密钥",
|
||||
"community_secretUpdated": "密码已更新为“{name}”",
|
||||
"@contacts_pathTraceTo": {
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pathTrace_you": "你",
|
||||
"pathTrace_failed": "路径追踪失败。",
|
||||
"pathTrace_notAvailable": "路径追踪不可用",
|
||||
"pathTrace_refreshTooltip": "刷新路径追踪",
|
||||
"contacts_pathTrace": "路径追踪",
|
||||
"contacts_ping": "ping",
|
||||
"contacts_repeaterPathTrace": "路径追踪到中继器",
|
||||
"contacts_repeaterPing": "Ping 中继器",
|
||||
"contacts_roomPathTrace": "路径追踪至房间服务器",
|
||||
"contacts_roomPing": "Ping 房间服务器",
|
||||
"contacts_chatTraceRoute": "路径追踪",
|
||||
"contacts_pathTraceTo": "追踪路由到 {name}"
|
||||
}
|
||||
|
||||
@@ -102,6 +102,46 @@ class Contact {
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
String get shortPubKeyHex {
|
||||
return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>";
|
||||
}
|
||||
|
||||
Uint8List? get traceRouteBytes {
|
||||
final pathBytes = _pathBytesForDisplay;
|
||||
Uint8List? traceBytes;
|
||||
|
||||
if(pathLength <= 0) {
|
||||
traceBytes = Uint8List(1);
|
||||
traceBytes[0] = publicKey[0];
|
||||
return traceBytes;
|
||||
}
|
||||
|
||||
if(type == advTypeRepeater || type == advTypeRoom) {
|
||||
final len = (pathBytes.length + pathBytes.length + 1);
|
||||
traceBytes = Uint8List(len);
|
||||
traceBytes[pathBytes.length] = publicKey[0];
|
||||
for (int i = 0; i < pathBytes.length; i++) {
|
||||
traceBytes[i] = pathBytes[i];
|
||||
if (i < pathBytes.length) {
|
||||
traceBytes[len-1-i] = pathBytes[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(pathBytes.length < 2) {
|
||||
return pathBytes[0] == 0 ? null : pathBytes;
|
||||
}
|
||||
final len = (pathBytes.length + pathBytes.length-1);
|
||||
traceBytes = Uint8List(len);
|
||||
for (int i = 0; i < pathBytes.length; i++) {
|
||||
traceBytes[i] = pathBytes[i];
|
||||
if (i < pathBytes.length-1) {
|
||||
traceBytes[len-1-i] = pathBytes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return traceBytes;
|
||||
}
|
||||
|
||||
Uint8List get _pathBytesForDisplay {
|
||||
if (pathOverride != null) {
|
||||
if (pathOverride! < 0) return Uint8List(0);
|
||||
|
||||
@@ -471,6 +471,10 @@ class AppSettingsScreen extends StatelessWidget {
|
||||
return context.l10n.appSettings_languageSk;
|
||||
case 'bg':
|
||||
return context.l10n.appSettings_languageBg;
|
||||
case 'ru':
|
||||
return context.l10n.appSettings_languageRu;
|
||||
case 'uk':
|
||||
return context.l10n.appSettings_languageUk;
|
||||
default:
|
||||
return context.l10n.appSettings_languageSystem;
|
||||
}
|
||||
@@ -547,6 +551,14 @@ class AppSettingsScreen extends StatelessWidget {
|
||||
title: Text(context.l10n.appSettings_languageBg),
|
||||
value: 'bg',
|
||||
),
|
||||
RadioListTile<String?>(
|
||||
title: Text(context.l10n.appSettings_languageRu),
|
||||
value: 'ru',
|
||||
),
|
||||
RadioListTile<String?>(
|
||||
title: Text(context.l10n.appSettings_languageUk),
|
||||
value: 'uk',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,11 +3,15 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../helpers/link_handler.dart';
|
||||
import '../helpers/reaction_helper.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/channel.dart';
|
||||
@@ -15,6 +19,7 @@ import '../models/channel_message.dart';
|
||||
import '../utils/emoji_utils.dart';
|
||||
import '../widgets/emoji_picker.dart';
|
||||
import '../widgets/gif_message.dart';
|
||||
import '../widgets/jump_to_bottom_button.dart';
|
||||
import '../widgets/gif_picker.dart';
|
||||
import 'channel_message_path_screen.dart';
|
||||
import 'map_screen.dart';
|
||||
@@ -33,42 +38,51 @@ class ChannelChatScreen extends StatefulWidget {
|
||||
|
||||
class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
final TextEditingController _textController = TextEditingController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final ChatScrollController _scrollController = ChatScrollController();
|
||||
final FocusNode _textFieldFocusNode = FocusNode();
|
||||
ChannelMessage? _replyingToMessage;
|
||||
final Map<String, GlobalKey> _messageKeys = {};
|
||||
bool _isLoadingOlder = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textFieldFocusNode.addListener(_onTextFieldFocusChange);
|
||||
_scrollController.onScrollNearTop = _loadOlderMessages;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
context.read<MeshCoreConnector>().setActiveChannel(widget.channel.index);
|
||||
|
||||
// Scroll to bottom when opening channel chat - use SchedulerBinding for next frame
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onTextFieldFocusChange() {
|
||||
if (_textFieldFocusNode.hasFocus && mounted) {
|
||||
_scrollController.handleKeyboardOpen();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadOlderMessages() async {
|
||||
if (_isLoadingOlder) return;
|
||||
setState(() => _isLoadingOlder = true);
|
||||
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
await connector.loadOlderChannelMessages(widget.channel.index);
|
||||
|
||||
if (mounted) {
|
||||
setState(() => _isLoadingOlder = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
context.read<MeshCoreConnector>().setActiveChannel(null);
|
||||
_textFieldFocusNode.removeListener(_onTextFieldFocusChange);
|
||||
_textFieldFocusNode.dispose();
|
||||
_textController.dispose();
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _scrollToBottom() {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _setReplyingTo(ChannelMessage message) {
|
||||
setState(() {
|
||||
_replyingToMessage = message;
|
||||
@@ -153,10 +167,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
builder: (context, connector, child) {
|
||||
final messages = connector.getChannelMessages(widget.channel);
|
||||
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToBottom();
|
||||
});
|
||||
|
||||
if (messages.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
@@ -190,20 +200,51 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemCount: messages.length,
|
||||
itemBuilder: (context, index) {
|
||||
final message = messages[index];
|
||||
if (!_messageKeys.containsKey(message.messageId)) {
|
||||
_messageKeys[message.messageId] = GlobalKey();
|
||||
}
|
||||
return Container(
|
||||
key: _messageKeys[message.messageId]!,
|
||||
child: _buildMessageBubble(message),
|
||||
);
|
||||
},
|
||||
// Reverse messages so newest appear at bottom with reverse: true
|
||||
final reversedMessages = messages.reversed.toList();
|
||||
final itemCount = reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
||||
|
||||
// Auto-scroll to bottom if user is already at bottom
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.scrollToBottomIfAtBottom();
|
||||
});
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
ListView.builder(
|
||||
reverse: true, // List grows from bottom up
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
// Loading indicator now appears at end (bottom) of reversed list
|
||||
if (_isLoadingOlder && index == itemCount - 1) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final messageIndex = index;
|
||||
final message = reversedMessages[messageIndex];
|
||||
if (!_messageKeys.containsKey(message.messageId)) {
|
||||
_messageKeys[message.messageId] = GlobalKey();
|
||||
}
|
||||
return Container(
|
||||
key: _messageKeys[message.messageId]!,
|
||||
child: _buildMessageBubble(message),
|
||||
);
|
||||
},
|
||||
),
|
||||
JumpToBottomButton(
|
||||
scrollController: _scrollController,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -241,7 +282,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
onTap: () => _showMessagePathInfo(message),
|
||||
onLongPress: () => _showMessageActions(message),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.all(4)
|
||||
: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.65,
|
||||
),
|
||||
@@ -255,15 +298,20 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isOutgoing) ...[
|
||||
Text(
|
||||
message.senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, top: 4, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
message.senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (gifId == null) const SizedBox(height: 4),
|
||||
],
|
||||
if (message.replyToMessageId != null) ...[
|
||||
_buildReplyPreview(message),
|
||||
@@ -272,60 +320,84 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
if (poi != null)
|
||||
_buildPoiMessage(context, poi, isOutgoing)
|
||||
else if (gifId != null)
|
||||
GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor: isOutgoing
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer.withValues(alpha: 0.7)
|
||||
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: Colors.transparent,
|
||||
fallbackTextColor: isOutgoing
|
||||
? Theme.of(context).colorScheme.onPrimaryContainer.withValues(alpha: 0.7)
|
||||
: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
message.text,
|
||||
Linkify(
|
||||
text: message.text,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
linkStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.green,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
options: const LinkifyOptions(
|
||||
humanize: false,
|
||||
defaultToHttps: false,
|
||||
),
|
||||
linkifiers: const [UrlLinkifier()],
|
||||
onOpen: (link) => LinkHandler.handleLinkTap(context, link.url),
|
||||
),
|
||||
if (displayPath.isNotEmpty) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'via ${_formatPathPrefixes(displayPath)}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.symmetric(horizontal: 8)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
'via ${_formatPathPrefixes(displayPath)}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
if (message.repeatCount > 0) ...[
|
||||
const SizedBox(width: 6),
|
||||
Icon(Icons.repeat, size: 12, color: Colors.grey[600]),
|
||||
const SizedBox(width: 2),
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, right: 8, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'${message.repeatCount}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
if (message.repeatCount > 0) ...[
|
||||
const SizedBox(width: 6),
|
||||
Icon(Icons.repeat, size: 12, color: Colors.grey[600]),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
'${message.repeatCount}',
|
||||
style: TextStyle(fontSize: 11, color: Colors.grey[600]),
|
||||
),
|
||||
],
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
message.status == ChannelMessageStatus.sent
|
||||
? Icons.check
|
||||
: message.status == ChannelMessageStatus.pending
|
||||
? Icons.schedule
|
||||
: Icons.error_outline,
|
||||
size: 14,
|
||||
color: message.status == ChannelMessageStatus.failed
|
||||
? Colors.red
|
||||
: Colors.grey[600],
|
||||
),
|
||||
],
|
||||
],
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
message.status == ChannelMessageStatus.sent
|
||||
? Icons.check
|
||||
: message.status == ChannelMessageStatus.pending
|
||||
? Icons.schedule
|
||||
: Icons.error_outline,
|
||||
size: 14,
|
||||
color: message.status == ChannelMessageStatus.failed
|
||||
? Colors.red
|
||||
: Colors.grey[600],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -364,8 +436,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor: previewTextColor,
|
||||
width: 120,
|
||||
height: 80,
|
||||
maxSize: 80,
|
||||
),
|
||||
);
|
||||
} else if (poi != null) {
|
||||
@@ -690,14 +761,16 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
width: 160,
|
||||
height: 110,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
maxSize: 160,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -711,9 +784,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
|
||||
return TextField(
|
||||
controller: _textController,
|
||||
focusNode: _textFieldFocusNode,
|
||||
inputFormatters: [
|
||||
Utf8LengthLimitingTextInputFormatter(maxBytes),
|
||||
],
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
border: OutlineInputBorder(
|
||||
@@ -803,14 +878,16 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
_setReplyingTo(message);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_reaction_outlined),
|
||||
title: Text(context.l10n.chat_addReaction),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showEmojiPicker(message);
|
||||
},
|
||||
),
|
||||
// Can't react to your own messages
|
||||
if (!message.isOutgoing)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_reaction_outlined),
|
||||
title: Text(context.l10n.chat_addReaction),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showEmojiPicker(message);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: Text(context.l10n.common_copy),
|
||||
@@ -852,9 +929,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
|
||||
void _sendReaction(ChannelMessage message, String emoji) {
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
// Send reaction with full messageId to find target, but parser will extract
|
||||
// lightweight reactionKey (timestamp_senderPrefix) for deduplication
|
||||
final reactionText = 'r:${message.messageId}:$emoji';
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji);
|
||||
if (emojiIndex == null) return; // Unknown emoji, skip
|
||||
final timestampSecs = message.timestamp.millisecondsSinceEpoch ~/ 1000;
|
||||
final hash = ReactionHelper.computeReactionHash(timestampSecs, message.senderName, message.text);
|
||||
final reactionText = 'r:$hash:$emojiIndex';
|
||||
connector.sendChannelMessage(widget.channel, reactionText);
|
||||
}
|
||||
|
||||
|
||||
+418
-370
File diff suppressed because it is too large
Load Diff
+176
-91
@@ -5,11 +5,15 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../helpers/reaction_helper.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
import '../helpers/link_handler.dart';
|
||||
import '../helpers/utf8_length_limiter.dart';
|
||||
import '../models/channel_message.dart';
|
||||
import '../models/contact.dart';
|
||||
@@ -20,6 +24,7 @@ import 'map_screen.dart';
|
||||
import '../utils/emoji_utils.dart';
|
||||
import '../widgets/emoji_picker.dart';
|
||||
import '../widgets/gif_message.dart';
|
||||
import '../widgets/jump_to_bottom_button.dart';
|
||||
import '../widgets/gif_picker.dart';
|
||||
import '../widgets/path_selection_dialog.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
@@ -36,25 +41,44 @@ class ChatScreen extends StatefulWidget {
|
||||
|
||||
class _ChatScreenState extends State<ChatScreen> {
|
||||
final _textController = TextEditingController();
|
||||
final _scrollController = ScrollController();
|
||||
final _scrollController = ChatScrollController();
|
||||
final _textFieldFocusNode = FocusNode();
|
||||
bool _isLoadingOlder = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textFieldFocusNode.addListener(_onTextFieldFocusChange);
|
||||
_scrollController.onScrollNearTop = _loadOlderMessages;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
context.read<MeshCoreConnector>().setActiveContact(widget.contact.publicKeyHex);
|
||||
|
||||
// Scroll to bottom when opening chat use SchedulerBinding for next frame
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.jumpTo(_scrollController.position.maxScrollExtent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onTextFieldFocusChange() {
|
||||
if (_textFieldFocusNode.hasFocus && mounted) {
|
||||
_scrollController.handleKeyboardOpen();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadOlderMessages() async {
|
||||
if (_isLoadingOlder) return;
|
||||
setState(() => _isLoadingOlder = true);
|
||||
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
await connector.loadOlderMessages(widget.contact.publicKeyHex);
|
||||
|
||||
if (mounted) {
|
||||
setState(() => _isLoadingOlder = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
context.read<MeshCoreConnector>().setActiveContact(null);
|
||||
_textFieldFocusNode.removeListener(_onTextFieldFocusChange);
|
||||
_textFieldFocusNode.dispose();
|
||||
_textController.dispose();
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
@@ -167,9 +191,16 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: messages.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildMessageList(messages, connector),
|
||||
child: Stack(
|
||||
children: [
|
||||
messages.isEmpty
|
||||
? _buildEmptyState()
|
||||
: _buildMessageList(messages, connector),
|
||||
JumpToBottomButton(
|
||||
scrollController: _scrollController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildInputBar(connector),
|
||||
],
|
||||
@@ -201,13 +232,37 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
}
|
||||
|
||||
Widget _buildMessageList(List<Message> messages, MeshCoreConnector connector) {
|
||||
// Reverse messages so newest appear at bottom with reverse: true
|
||||
final reversedMessages = messages.reversed.toList();
|
||||
final itemCount = reversedMessages.length + (_isLoadingOlder ? 1 : 0);
|
||||
|
||||
// Auto-scroll to bottom if user is already at bottom
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.scrollToBottomIfAtBottom();
|
||||
});
|
||||
|
||||
return ListView.builder(
|
||||
reverse: true, // List grows from bottom up
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16),
|
||||
itemCount: messages.length,
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
// Loading indicator now appears at end (bottom) of reversed list
|
||||
if (_isLoadingOlder && index == itemCount - 1) {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
final messageIndex = index;
|
||||
Contact contact = widget.contact;
|
||||
final message = messages[index];
|
||||
final message = reversedMessages[messageIndex];
|
||||
String fourByteHex = '';
|
||||
if (widget.contact.type == advTypeRoom) {
|
||||
contact = _resolveContactFrom4Bytes(
|
||||
@@ -256,13 +311,15 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
width: 160,
|
||||
height: 110,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: colorScheme.surfaceContainerHighest,
|
||||
fallbackTextColor:
|
||||
colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
maxSize: 160,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -276,9 +333,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
return TextField(
|
||||
controller: _textController,
|
||||
focusNode: _textFieldFocusNode,
|
||||
inputFormatters: [
|
||||
Utf8LengthLimitingTextInputFormatter(maxBytes),
|
||||
],
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.chat_typeMessage,
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -336,16 +395,6 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
text,
|
||||
);
|
||||
_textController.clear();
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 100), () {
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -802,14 +851,16 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_reaction_outlined),
|
||||
title: Text(context.l10n.chat_addReaction),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showEmojiPicker(message);
|
||||
},
|
||||
),
|
||||
// Can't react to your own messages
|
||||
if (!message.isOutgoing)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_reaction_outlined),
|
||||
title: Text(context.l10n.chat_addReaction),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showEmojiPicker(message, contact);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: Text(context.l10n.common_copy),
|
||||
@@ -883,25 +934,29 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
void _showEmojiPicker(Message message) {
|
||||
void _showEmojiPicker(Message message, Contact senderContact) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => EmojiPicker(
|
||||
onEmojiSelected: (emoji) {
|
||||
_sendReaction(message, emoji);
|
||||
_sendReaction(message, senderContact, emoji);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _sendReaction(Message message, String emoji) {
|
||||
void _sendReaction(Message message, Contact senderContact, String emoji) {
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
// Send reaction with messageId if available, otherwise use lightweight format
|
||||
// Parser will extract reactionKey (timestamp_senderPrefix) for deduplication
|
||||
final messageId = message.messageId ??
|
||||
'${message.timestamp.millisecondsSinceEpoch}_${message.senderKeyHex.substring(0, 8)}';
|
||||
final reactionText = 'r:$messageId:$emoji';
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji);
|
||||
if (emojiIndex == null) return; // Unknown emoji, skip
|
||||
final timestampSecs = message.timestamp.millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
// For room servers, include sender name (like channels) since multiple users
|
||||
// For 1:1 chats, sender is implicit (null)
|
||||
final senderName = widget.contact.type == advTypeRoom ? senderContact.name : null;
|
||||
final hash = ReactionHelper.computeReactionHash(timestampSecs, senderName, message.text);
|
||||
final reactionText = 'r:$hash:$emojiIndex';
|
||||
connector.sendMessage(widget.contact, reactionText);
|
||||
}
|
||||
}
|
||||
@@ -957,7 +1012,9 @@ class _MessageBubble extends StatelessWidget {
|
||||
],
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.all(4)
|
||||
: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.65,
|
||||
),
|
||||
@@ -969,75 +1026,103 @@ class _MessageBubble extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isOutgoing) ...[
|
||||
Text(
|
||||
senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, top: 4, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
senderName,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (gifId == null) const SizedBox(height: 4),
|
||||
],
|
||||
if (poi != null)
|
||||
_buildPoiMessage(context, poi, textColor, metaColor)
|
||||
else if (gifId != null)
|
||||
GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: bubbleColor,
|
||||
fallbackTextColor: textColor.withValues(alpha: 0.7),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: GifMessage(
|
||||
url: 'https://media.giphy.com/media/$gifId/giphy.gif',
|
||||
backgroundColor: Colors.transparent,
|
||||
fallbackTextColor: textColor.withValues(alpha: 0.7),
|
||||
),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
messageText,
|
||||
Linkify(
|
||||
text: messageText,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
),
|
||||
linkStyle: const TextStyle(
|
||||
color: Colors.green,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
options: const LinkifyOptions(
|
||||
humanize: false,
|
||||
defaultToHttps: false,
|
||||
),
|
||||
linkifiers: const [UrlLinkifier()],
|
||||
onOpen: (link) => LinkHandler.handleLinkTap(context, link.url),
|
||||
),
|
||||
if (isOutgoing && message.retryCount > 0) ...[
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
context.l10n.chat_retryCount(message.retryCount, 4),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.symmetric(horizontal: 8)
|
||||
: EdgeInsets.zero,
|
||||
child: Text(
|
||||
context.l10n.chat_retryCount(message.retryCount, 4),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
Wrap(
|
||||
spacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
),
|
||||
),
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
_buildStatusIcon(metaColor),
|
||||
],
|
||||
if (message.tripTimeMs != null &&
|
||||
message.status == MessageStatus.delivered) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.speed,
|
||||
size: 10,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
),
|
||||
Padding(
|
||||
padding: gifId != null
|
||||
? const EdgeInsets.only(left: 8, right: 8, bottom: 4)
|
||||
: EdgeInsets.zero,
|
||||
child: Wrap(
|
||||
spacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'${(message.tripTimeMs! / 1000).toStringAsFixed(1)}s',
|
||||
_formatTime(message.timestamp),
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
fontSize: 10,
|
||||
color: metaColor,
|
||||
),
|
||||
),
|
||||
if (isOutgoing) ...[
|
||||
const SizedBox(width: 4),
|
||||
_buildStatusIcon(metaColor),
|
||||
],
|
||||
if (message.tripTimeMs != null &&
|
||||
message.status == MessageStatus.delivered) ...[
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.speed,
|
||||
size: 10,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
),
|
||||
Text(
|
||||
'${(message.tripTimeMs! / 1000).toStringAsFixed(1)}s',
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
color: isOutgoing ? metaColor : Colors.green[700],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:meshcore_open/widgets/path_trace_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
@@ -51,7 +53,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
final ContactGroupStore _groupStore = ContactGroupStore();
|
||||
List<ContactGroup> _groups = [];
|
||||
Timer? _searchDebounce;
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -313,6 +315,14 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
return matchesContactQuery(contact, _searchQuery);
|
||||
}).toList();
|
||||
|
||||
// Filter out own node from the list
|
||||
if (connector.selfPublicKey != null) {
|
||||
final selfPubKeyHex = pubKeyToHex(connector.selfPublicKey!);
|
||||
filtered = filtered.where((contact) {
|
||||
return contact.publicKeyHex != selfPubKeyHex;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
if (_typeFilter != ContactTypeFilter.all) {
|
||||
filtered = filtered.where(_matchesTypeFilter).toList();
|
||||
}
|
||||
@@ -752,7 +762,19 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (isRepeater)
|
||||
if (isRepeater) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.radar, color: Colors.green),
|
||||
title: contact.pathLength > 0 ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping),
|
||||
onTap: () {
|
||||
showDialog(context: context, builder: (context) {
|
||||
return PathTraceDialog(
|
||||
title: contact.pathLength > 0 ? context.l10n.contacts_repeaterPathTrace : context.l10n.contacts_repeaterPing,
|
||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||
);
|
||||
});
|
||||
}
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.cell_tower, color: Colors.orange),
|
||||
title: Text(context.l10n.contacts_manageRepeater),
|
||||
@@ -761,7 +783,19 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
_showRepeaterLogin(context, contact);
|
||||
},
|
||||
)
|
||||
else if (isRoom) ...[
|
||||
]else if (isRoom) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.radar, color: Colors.green),
|
||||
title: contact.pathLength > 0 ? Text(context.l10n.contacts_pathTrace) : Text(context.l10n.contacts_ping),
|
||||
onTap: () {
|
||||
showDialog(context: context, builder: (context) {
|
||||
return PathTraceDialog(
|
||||
title: contact.pathLength > 0 ? context.l10n.contacts_roomPathTrace : context.l10n.contacts_roomPing,
|
||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||
);
|
||||
});
|
||||
}
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.room, color: Colors.blue),
|
||||
title: Text(context.l10n.contacts_roomLogin),
|
||||
@@ -778,7 +812,20 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
_showRoomLogin(context, contact, RoomLoginDestination.management);
|
||||
},
|
||||
),
|
||||
] else
|
||||
] else ...[
|
||||
if(contact.pathLength > 0)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.radar, color: Colors.green),
|
||||
title: Text(context.l10n.contacts_chatTraceRoute),
|
||||
onTap: () {
|
||||
showDialog(context: context, builder: (context) {
|
||||
return PathTraceDialog(
|
||||
title: context.l10n.contacts_pathTraceTo(contact.name),
|
||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||
);
|
||||
});
|
||||
}
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.chat),
|
||||
title: Text(context.l10n.contacts_openChat),
|
||||
@@ -798,6 +845,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
||||
_confirmDelete(context, connector, contact);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -852,8 +900,6 @@ class _ContactTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final shotPublicKey =
|
||||
"<${contact.publicKeyHex.substring(0, 8)}...${contact.publicKeyHex.substring(contact.publicKeyHex.length - 8)}>";
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: _getTypeColor(contact.type),
|
||||
@@ -861,23 +907,32 @@ class _ContactTile extends StatelessWidget {
|
||||
),
|
||||
title: Text(contact.name),
|
||||
subtitle: Text(
|
||||
'${contact.typeLabel} • ${contact.pathLabel} $shotPublicKey',
|
||||
'${contact.typeLabel} • ${contact.pathLabel} ${contact.shortPubKeyHex}',
|
||||
),
|
||||
trailing: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (unreadCount > 0) ...[
|
||||
UnreadBadge(count: unreadCount),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
_formatLastSeen(context, lastSeen),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
// Clamp text scaling in trailing section to prevent overflow while
|
||||
// maintaining accessibility. Primary content (title/subtitle) scales normally.
|
||||
trailing: MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(
|
||||
textScaler: TextScaler.linear(
|
||||
MediaQuery.textScalerOf(context).scale(1.0).clamp(1.0, 1.3),
|
||||
),
|
||||
if (contact.hasLocation)
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (unreadCount > 0) ...[
|
||||
UnreadBadge(count: unreadCount),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
_formatLastSeen(context, lastSeen),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
if (contact.hasLocation)
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
|
||||
],
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
|
||||
@@ -354,6 +354,7 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _showFilterDialog(context, settingsService),
|
||||
tooltip: context.l10n.map_filterNodes,
|
||||
child: const Icon(Icons.filter_list),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -73,7 +73,7 @@ class RepeaterHubScreen extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'<${repeater.publicKeyHex.substring(0, 8)}...${repeater.publicKeyHex.substring(repeater.publicKeyHex.length - 8)}>',
|
||||
repeater.shortPubKeyHex,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -8,9 +8,47 @@ import '../widgets/device_tile.dart';
|
||||
import 'contacts_screen.dart';
|
||||
|
||||
/// Screen for scanning and connecting to MeshCore devices
|
||||
class ScannerScreen extends StatelessWidget {
|
||||
class ScannerScreen extends StatefulWidget {
|
||||
const ScannerScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ScannerScreen> createState() => _ScannerScreenState();
|
||||
}
|
||||
|
||||
class _ScannerScreenState extends State<ScannerScreen> {
|
||||
bool _changedNavigation = false;
|
||||
late final VoidCallback _connectionListener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
|
||||
_connectionListener = () {
|
||||
if (connector.state == MeshCoreConnectionState.disconnected) {
|
||||
_changedNavigation = false;
|
||||
} else if (connector.state == MeshCoreConnectionState.connected && !_changedNavigation) {
|
||||
_changedNavigation = true;
|
||||
if (mounted) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ContactsScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
connector.addListener(_connectionListener);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
connector.removeListener(_connectionListener);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -161,15 +199,6 @@ final l10n = context.l10n;
|
||||
? result.device.platformName
|
||||
: result.advertisementData.advName;
|
||||
await connector.connect(result.device, displayName: name);
|
||||
|
||||
if (context.mounted && connector.isConnected) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ContactsScreen(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
@@ -156,6 +156,8 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
return 'CMD_GET_RADIO_SETTINGS';
|
||||
case cmdSetCustomVar:
|
||||
return 'CMD_SET_CUSTOM_VAR';
|
||||
case cmdSendTracePath:
|
||||
return 'CMD_SEND_TRACE_PATH';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -195,6 +197,8 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
return 'RESP_CODE_CHANNEL_INFO';
|
||||
case respCodeRadioSettings:
|
||||
return 'RESP_CODE_RADIO_SETTINGS';
|
||||
case pushCodeTraceData:
|
||||
return 'PUSH_CODE_TRACE_DATA';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class NotificationService {
|
||||
await _notifications.show(
|
||||
contactId?.hashCode ?? 0,
|
||||
'New message from $contactName',
|
||||
message.length > 100 ? '${message.substring(0, 100)}...' : message,
|
||||
message,
|
||||
notificationDetails,
|
||||
payload: 'message:$contactId',
|
||||
);
|
||||
@@ -203,10 +203,10 @@ class NotificationService {
|
||||
macOS: macDetails,
|
||||
);
|
||||
|
||||
final preview = _truncateMessage(message, 30);
|
||||
final preview = message.trim();
|
||||
final body = preview.isEmpty
|
||||
? 'Received new message'
|
||||
: 'Received new message: $preview';
|
||||
: preview;
|
||||
|
||||
await _notifications.show(
|
||||
channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
@@ -217,12 +217,6 @@ class NotificationService {
|
||||
);
|
||||
}
|
||||
|
||||
String _truncateMessage(String message, int maxLength) {
|
||||
final trimmed = message.trim();
|
||||
if (trimmed.length <= maxLength) return trimmed;
|
||||
return '${trimmed.substring(0, maxLength)}...';
|
||||
}
|
||||
|
||||
void _onNotificationTapped(NotificationResponse response) {
|
||||
final payload = response.payload;
|
||||
if (payload != null) {
|
||||
|
||||
@@ -12,32 +12,32 @@ class EmojiPicker extends StatelessWidget {
|
||||
|
||||
static const List<String> quickEmojis = ['👍', '❤️', '😂', '🎉', '👏', '🔥'];
|
||||
|
||||
static const List<String> _smileys = [
|
||||
static const List<String> smileys = [
|
||||
'😀', '😃', '😄', '😁', '😅', '😂', '🤣', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘',
|
||||
'😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🥸', '🤩', '🥳', '😏',
|
||||
'😒', '😞', '😔', '😟', '😕', '🙁', '😣', '😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡',
|
||||
'🤬', '🤯', '😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🤗', '🤔', '🤭', '🤫', '🤥', '😶',
|
||||
];
|
||||
static const List<String> _gestures = [
|
||||
static const List<String> gestures = [
|
||||
'👍', '👎', '👊', '✊', '🤛', '🤜', '🤞', '✌️', '🤟', '🤘', '👌', '🤌', '🤏', '👈', '👉', '👆',
|
||||
'👇', '☝️', '👋', '🤚', '🖐️', '✋', '🖖', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳',
|
||||
'👇', '☝️', '👋', '🤚', '🖐️', '✋', '🖖', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳', '💪',
|
||||
];
|
||||
static const List<String> _hearts = [
|
||||
static const List<String> hearts = [
|
||||
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔', '❤️🔥', '❤️🩹', '💕', '💞', '💓', '💗',
|
||||
'💖', '💘', '💝', '💟', '💌', '💢', '💥', '💫', '💦', '💨', '🕳️', '💬', '👁️🗨️', '🗨️', '🗯️', '💭',
|
||||
];
|
||||
static const List<String> _objects = [
|
||||
static const List<String> objects = [
|
||||
'🎉', '🎊', '🎈', '🎁', '🎀', '🪅', '🪆', '🏆', '🥇', '🥈', '🥉', '⚽', '⚾', '🥎', '🏀', '🏐',
|
||||
'🏈', '🏉', '🎾', '🥏', '🎳', '🏏', '🏑', '🏒', '🥍', '🏓', '🏸', '🥊', '🥋', '🥅', '⛳', '🔥',
|
||||
'⭐', '🌟', '✨', '⚡', '💡', '🔦', '🏮', '🪔', '📱', '💻', '⌚', '📷', '📺', '📻', '🎵', '🎶',
|
||||
'⭐', '🌟', '✨', '⚡', '💡', '🔦', '🏮', '🪔', '📱', '💻', '⌚', '📷', '📺', '📻', '🎵', '🎶', '🚀',
|
||||
];
|
||||
|
||||
Map<String, List<String>> _emojiCategories(AppLocalizations l10n) {
|
||||
return {
|
||||
l10n.emojiCategorySmileys: _smileys,
|
||||
l10n.emojiCategoryGestures: _gestures,
|
||||
l10n.emojiCategoryHearts: _hearts,
|
||||
l10n.emojiCategoryObjects: _objects,
|
||||
l10n.emojiCategorySmileys: smileys,
|
||||
l10n.emojiCategoryGestures: gestures,
|
||||
l10n.emojiCategoryHearts: hearts,
|
||||
l10n.emojiCategoryObjects: objects,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,16 +6,14 @@ class GifMessage extends StatefulWidget {
|
||||
final String url;
|
||||
final Color backgroundColor;
|
||||
final Color fallbackTextColor;
|
||||
final double width;
|
||||
final double height;
|
||||
final double maxSize;
|
||||
|
||||
const GifMessage({
|
||||
super.key,
|
||||
required this.url,
|
||||
required this.backgroundColor,
|
||||
required this.fallbackTextColor,
|
||||
this.width = 200,
|
||||
this.height = 140,
|
||||
this.maxSize = 200,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -122,6 +120,28 @@ class _GifMessageState extends State<GifMessage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Calculate display size based on image aspect ratio
|
||||
// Use 4:3 placeholder aspect ratio during loading to minimize layout shifts
|
||||
double displayWidth = widget.maxSize;
|
||||
double displayHeight = widget.maxSize * 0.75;
|
||||
|
||||
if (_image != null) {
|
||||
final imageWidth = _image!.width.toDouble();
|
||||
final imageHeight = _image!.height.toDouble();
|
||||
final aspectRatio = imageWidth / imageHeight;
|
||||
|
||||
// Fit within maxSize, calculating dimensions from aspect ratio
|
||||
if (aspectRatio >= 1) {
|
||||
// Wider than tall: constrain by width
|
||||
displayWidth = widget.maxSize;
|
||||
displayHeight = displayWidth / aspectRatio;
|
||||
} else {
|
||||
// Taller than wide: constrain by height
|
||||
displayHeight = widget.maxSize;
|
||||
displayWidth = displayHeight * aspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
Widget content;
|
||||
|
||||
if (_error != null) {
|
||||
@@ -151,33 +171,30 @@ class _GifMessageState extends State<GifMessage> {
|
||||
} else {
|
||||
content = RawImage(
|
||||
image: _image,
|
||||
fit: BoxFit.cover,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: BoxFit.contain,
|
||||
width: displayWidth,
|
||||
height: displayHeight,
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: _togglePause,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
color: widget.backgroundColor,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
content,
|
||||
if (_isPaused && _image != null)
|
||||
Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
child: const Center(
|
||||
child: Icon(Icons.pause, color: Colors.white70, size: 28),
|
||||
),
|
||||
child: Container(
|
||||
color: widget.backgroundColor,
|
||||
width: displayWidth,
|
||||
height: displayHeight,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
content,
|
||||
if (_isPaused && _image != null)
|
||||
Container(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
child: const Center(
|
||||
child: Icon(Icons.pause, color: Colors.white70, size: 28),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../helpers/chat_scroll_controller.dart';
|
||||
|
||||
class JumpToBottomButton extends StatelessWidget {
|
||||
final ChatScrollController scrollController;
|
||||
|
||||
const JumpToBottomButton({
|
||||
super.key,
|
||||
required this.scrollController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: scrollController.showJumpToBottom,
|
||||
builder: (context, show, _) {
|
||||
if (!show) return const SizedBox.shrink();
|
||||
return Positioned(
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: scrollController.jumpToBottom,
|
||||
child: const Icon(Icons.keyboard_arrow_down),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../widgets/snr_indicator.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
class PathTraceDialog extends StatefulWidget {
|
||||
|
||||
const PathTraceDialog({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.path,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final Uint8List path;
|
||||
|
||||
@override
|
||||
State<PathTraceDialog> createState() => _PathTraceDialogState();
|
||||
}
|
||||
|
||||
class _PathTraceDialogState extends State<PathTraceDialog> {
|
||||
StreamSubscription<Uint8List>? _frameSubscription;
|
||||
Timer? _timeoutTimer;
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _failed2Loaded = false;
|
||||
bool _hasData = false;
|
||||
Uint8List _pathData = Uint8List(0);
|
||||
Uint8List _snrData = Uint8List(0) ;
|
||||
Map<int, Contact> _pathContacts = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_setupFrameListener();
|
||||
_doPathTrace();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_frameSubscription?.cancel();
|
||||
_timeoutTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _doPathTrace() async {
|
||||
if(mounted) {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_failed2Loaded = false;
|
||||
});
|
||||
}
|
||||
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
final frame = buildTraceReq(
|
||||
DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
0, //flags
|
||||
0, //auth
|
||||
payload: widget.path,
|
||||
);
|
||||
connector.sendFrame(frame);
|
||||
}
|
||||
|
||||
void _setupFrameListener() {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
Uint8List tagData = Uint8List(4);
|
||||
// Listen for incoming text messages from the repeater
|
||||
_frameSubscription = connector.receivedFrames.listen((frame) {
|
||||
if (frame.isEmpty) return;
|
||||
final frameBuffer = BufferReader(frame);
|
||||
final code = frameBuffer.readUInt8();
|
||||
|
||||
if (code == respCodeSent) {
|
||||
frameBuffer.skipBytes(1); //reserved
|
||||
tagData = frameBuffer.readBytes(4);
|
||||
final timeoutSeconds = frameBuffer.readUInt32LE();
|
||||
|
||||
// Start timeout timer for trace response
|
||||
_timeoutTimer?.cancel();
|
||||
_timeoutTimer = Timer(Duration(milliseconds: timeoutSeconds), () {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_failed2Loaded = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Check if it's a binary response
|
||||
if (code == pushCodeTraceData && listEquals(frame.sublist(4, 8), tagData)) {
|
||||
_timeoutTimer?.cancel();
|
||||
if (!mounted) return;
|
||||
frameBuffer.skipBytes(3); //reserved + path length + flag
|
||||
if(listEquals(frameBuffer.readBytes(4), tagData)){
|
||||
_handleTraceResponse(frame);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleTraceResponse(Uint8List frame)async {
|
||||
final connector = Provider.of<MeshCoreConnector>(context, listen: false);
|
||||
|
||||
final buffer = BufferReader(frame);
|
||||
buffer.skipBytes(2); // Skip push code and reserved byte
|
||||
int pathLength = buffer.readUInt8();
|
||||
buffer.skipBytes(5); // Skip Flag byte and tag data
|
||||
buffer.skipBytes(4); // Skip auth code
|
||||
Uint8List pathData = buffer.readBytes(pathLength);
|
||||
Uint8List snrData = buffer.readRemainingBytes();
|
||||
|
||||
Map<int, Contact> pathContacts = {};
|
||||
|
||||
connector.contacts.where((c) => c.type != advTypeChat).forEach((
|
||||
repeater,
|
||||
) {
|
||||
for (var neighbourData in pathData) {
|
||||
if (listEquals(
|
||||
repeater.publicKey.sublist(0, 1),
|
||||
Uint8List.fromList([neighbourData]),
|
||||
)) {
|
||||
pathContacts[neighbourData] = repeater;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_hasData = true;
|
||||
_pathData = pathData;
|
||||
_snrData = snrData;
|
||||
_pathContacts = pathContacts;
|
||||
});
|
||||
}
|
||||
|
||||
String formatDirectionText(int index) {
|
||||
if (index == 0 || index == _snrData.length - 1) {
|
||||
if (index == 0) {
|
||||
return context.l10n.pathTrace_you;
|
||||
} else {
|
||||
return _pathContacts[_pathData[_pathData.length - 1]]?.name ?? "0x${_pathData[_pathData.length - 1].toRadixString(16).toUpperCase()}";
|
||||
}
|
||||
} else {
|
||||
return _pathContacts[_pathData[index-1]]?.name ?? "0x${_pathData[index-1].toRadixString(16).toUpperCase()}";
|
||||
}
|
||||
}
|
||||
String formatDirectionSubText(int index) {
|
||||
if (index == 0 || index == _snrData.length - 1) {
|
||||
if (index == 0) {
|
||||
return _pathContacts[_pathData[0]]?.name ?? "0x${_pathData[0].toRadixString(16).toUpperCase()}";
|
||||
} else {
|
||||
return context.l10n.pathTrace_you;
|
||||
}
|
||||
} else {
|
||||
return _pathContacts[_pathData[index]]?.name ?? "0x${_pathData[index].toRadixString(16).toUpperCase()}";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return AlertDialog(
|
||||
title: Column( children: [
|
||||
FittedBox(fit: BoxFit.scaleDown, child: Text(widget.title, style: const TextStyle(fontSize: 24))),
|
||||
if(_failed2Loaded)
|
||||
Text(l10n.pathTrace_failed, style: TextStyle(fontSize: 14, color: Theme.of(context).colorScheme.error),),
|
||||
],
|
||||
),
|
||||
content: SafeArea(
|
||||
child: RefreshIndicator(
|
||||
onRefresh: _doPathTrace,
|
||||
child: !_hasData
|
||||
? Center(
|
||||
child: Text(l10n.pathTrace_notAvailable),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: _snrData.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: index >= _snrData.length / 2 ? Icon(Icons.call_received) : Icon(Icons.call_made),
|
||||
title: Text(
|
||||
formatDirectionText(index), style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
subtitle: Text(
|
||||
formatDirectionSubText(index),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
trailing: SNRIcon(snr: _snrData[index].toSigned(8) / 4.0),
|
||||
onTap: () {
|
||||
// Handle item tap
|
||||
},
|
||||
),
|
||||
if (index < _snrData.length - 1) const Divider(height: 0.0),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: _isLoading
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.refresh),
|
||||
onPressed: _isLoading ? null : _doPathTrace,
|
||||
tooltip: l10n.pathTrace_refreshTooltip,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(l10n.common_close),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -322,7 +322,9 @@ class _RepeaterLoginDialogState extends State<RepeaterLoginDialog> {
|
||||
}
|
||||
},
|
||||
onSubmitted: (_) => _handleLogin(),
|
||||
autofocus: _passwordController.text.isEmpty,
|
||||
autofocus: !(defaultTargetPlatform == TargetPlatform.android ||
|
||||
defaultTargetPlatform == TargetPlatform.iOS) &&
|
||||
_passwordController.text.isEmpty,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CheckboxListTile(
|
||||
|
||||
@@ -261,7 +261,8 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -292,7 +293,9 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
||||
),
|
||||
),
|
||||
onSubmitted: (_) => _handleLogin(),
|
||||
autofocus: _passwordController.text.isEmpty,
|
||||
autofocus: !(defaultTargetPlatform == TargetPlatform.android ||
|
||||
defaultTargetPlatform == TargetPlatform.iOS) &&
|
||||
_passwordController.text.isEmpty,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
CheckboxListTile(
|
||||
@@ -382,6 +385,7 @@ class _RoomLoginDialogState extends State<RoomLoginDialog> {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "ephemeral/Flutter-Generated.xcconfig"
|
||||
|
||||
@@ -9,9 +9,9 @@ import flutter_blue_plus_darwin
|
||||
import flutter_local_notifications
|
||||
import mobile_scanner
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import url_launcher_macos
|
||||
import wakelock_plus
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
@@ -19,8 +19,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
platform :osx, '10.15'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_macos_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
|
||||
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_macos_build_settings(target)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
PODS:
|
||||
- flutter_blue_plus_darwin (0.0.2):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- FlutterMacOS
|
||||
- FlutterMacOS (1.0.0)
|
||||
- mobile_scanner (6.0.2):
|
||||
- FlutterMacOS
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqflite_darwin (0.0.4):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- flutter_blue_plus_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin`)
|
||||
- flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
flutter_blue_plus_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_blue_plus_darwin/darwin
|
||||
flutter_local_notifications:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos
|
||||
FlutterMacOS:
|
||||
:path: Flutter/ephemeral
|
||||
mobile_scanner:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
path_provider_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
sqflite_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
wakelock_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
flutter_blue_plus_darwin: 20a08bfeaa0f7804d524858d3d8744bcc1b6dbc3
|
||||
flutter_local_notifications: 13862b132e32eb858dea558a86d45d08daeacfe7
|
||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||
mobile_scanner: 0e365ed56cad24f28c0fd858ca04edefb40dfac3
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
|
||||
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
@@ -27,6 +27,8 @@
|
||||
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
|
||||
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
|
||||
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
|
||||
99C5B380294D2DE19A818101 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B665683D805EE21638F484F2 /* Pods_RunnerTests.framework */; };
|
||||
D7DDCBD47F2955423D77927D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F985DDB6BE5BEB6B545DE9A /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -60,11 +62,13 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
00F1FE94A1827B8A00BD3DB9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
0F985DDB6BE5BEB6B545DE9A /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
|
||||
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
|
||||
33CC10ED2044A3C60003C045 /* meshcore_open.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "meshcore_open.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10ED2044A3C60003C045 /* meshcore_open.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = meshcore_open.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
|
||||
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
|
||||
@@ -76,8 +80,14 @@
|
||||
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
|
||||
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
|
||||
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
|
||||
4172BCCDFD1E1404F7155426 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
|
||||
96DE804777D5630B2C6952B5 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
|
||||
B665683D805EE21638F484F2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BEFF4DDC60AFB628205F8E82 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D99E941424F19B7B9AA1B968 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
EA5A89F8C49904B995EFAA24 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -85,6 +95,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
99C5B380294D2DE19A818101 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -92,6 +103,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D7DDCBD47F2955423D77927D /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -125,6 +137,7 @@
|
||||
331C80D6294CF71000263BE5 /* RunnerTests */,
|
||||
33CC10EE2044A3C60003C045 /* Products */,
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */,
|
||||
73DBB8BFF247FD65EEC878CC /* Pods */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -172,9 +185,25 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
73DBB8BFF247FD65EEC878CC /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BEFF4DDC60AFB628205F8E82 /* Pods-Runner.debug.xcconfig */,
|
||||
4172BCCDFD1E1404F7155426 /* Pods-Runner.release.xcconfig */,
|
||||
00F1FE94A1827B8A00BD3DB9 /* Pods-Runner.profile.xcconfig */,
|
||||
96DE804777D5630B2C6952B5 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
EA5A89F8C49904B995EFAA24 /* Pods-RunnerTests.release.xcconfig */,
|
||||
D99E941424F19B7B9AA1B968 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0F985DDB6BE5BEB6B545DE9A /* Pods_Runner.framework */,
|
||||
B665683D805EE21638F484F2 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -186,6 +215,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
7DEC542F9A4811B2EEDCB8C1 /* [CP] Check Pods Manifest.lock */,
|
||||
331C80D1294CF70F00263BE5 /* Sources */,
|
||||
331C80D2294CF70F00263BE5 /* Frameworks */,
|
||||
331C80D3294CF70F00263BE5 /* Resources */,
|
||||
@@ -204,11 +234,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
79D67F01E273245A9C69C0B6 /* [CP] Check Pods Manifest.lock */,
|
||||
33CC10E92044A3C60003C045 /* Sources */,
|
||||
33CC10EA2044A3C60003C045 /* Frameworks */,
|
||||
33CC10EB2044A3C60003C045 /* Resources */,
|
||||
33CC110E2044A8840003C045 /* Bundle Framework */,
|
||||
3399D490228B24CF009A79C7 /* ShellScript */,
|
||||
306490712F2EAA29CA421662 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -291,6 +323,23 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
306490712F2EAA29CA421662 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
@@ -329,6 +378,50 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
|
||||
};
|
||||
79D67F01E273245A9C69C0B6 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
7DEC542F9A4811B2EEDCB8C1 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -380,6 +473,7 @@
|
||||
/* Begin XCBuildConfiguration section */
|
||||
331C80DB294CF71000263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 96DE804777D5630B2C6952B5 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -394,6 +488,7 @@
|
||||
};
|
||||
331C80DC294CF71000263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = EA5A89F8C49904B995EFAA24 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
@@ -408,6 +503,7 @@
|
||||
};
|
||||
331C80DD294CF71000263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = D99E941424F19B7B9AA1B968 /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
|
||||
@@ -4,4 +4,7 @@
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
+148
-12
@@ -97,6 +97,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_assets:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_assets
|
||||
sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -157,10 +165,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -234,10 +242,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_blue_plus_winrt
|
||||
sha256: "0c87ca5bdf1a110d42847edeca8fbb11a9701738dc8526aefbb2a115bea29aef"
|
||||
sha256: "34be2d8e23d5881b46accebb0e71025f7d52869d72ea98b5082c20764e06aa80"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.10"
|
||||
version: "0.0.16"
|
||||
flutter_cache_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -262,6 +270,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
flutter_linkify:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_linkify
|
||||
sha256: "74669e06a8f358fee4512b4320c0b80e51cffc496607931de68d28f099254073"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -317,6 +333,22 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
hooks:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hooks
|
||||
sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -361,10 +393,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
version: "4.10.0"
|
||||
latlong2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -397,6 +429,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
linkify:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: linkify
|
||||
sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -421,6 +461,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -461,6 +509,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.11"
|
||||
native_toolchain_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: native_toolchain_c
|
||||
sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.4"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -469,6 +525,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
objective_c:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: objective_c
|
||||
sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.5"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -521,10 +585,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
|
||||
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -613,6 +677,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5+1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -649,10 +721,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
||||
sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.18"
|
||||
version: "2.4.20"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -818,6 +890,70 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.28"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.6"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.5"
|
||||
uuid:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -907,5 +1043,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.9.2 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
dart: ">=3.10.3 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
|
||||
+3
-1
@@ -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: 0.4.0+4
|
||||
version: 5.0.0+5
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
@@ -55,6 +55,8 @@ dependencies:
|
||||
package_info_plus: ^8.0.0
|
||||
mobile_scanner: ^6.0.0 # QR/barcode scanning
|
||||
qr_flutter: ^4.1.0 # QR code generation
|
||||
url_launcher: ^6.3.0 # Launch URLs in system browser
|
||||
flutter_linkify: ^6.0.0 # Auto-detect and linkify URLs in text
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -0,0 +1,404 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_open/helpers/reaction_helper.dart';
|
||||
import 'package:meshcore_open/widgets/emoji_picker.dart';
|
||||
|
||||
void main() {
|
||||
group('ReactionHelper', () {
|
||||
group('reactionEmojis', () {
|
||||
test('should contain all emoji categories', () {
|
||||
final emojis = ReactionHelper.reactionEmojis;
|
||||
|
||||
// Should contain quickEmojis
|
||||
for (final emoji in EmojiPicker.quickEmojis) {
|
||||
expect(emojis.contains(emoji), isTrue, reason: 'Missing quick emoji: $emoji');
|
||||
}
|
||||
|
||||
// Should contain smileys
|
||||
for (final emoji in EmojiPicker.smileys) {
|
||||
expect(emojis.contains(emoji), isTrue, reason: 'Missing smiley: $emoji');
|
||||
}
|
||||
|
||||
// Should contain gestures
|
||||
for (final emoji in EmojiPicker.gestures) {
|
||||
expect(emojis.contains(emoji), isTrue, reason: 'Missing gesture: $emoji');
|
||||
}
|
||||
|
||||
// Should contain hearts
|
||||
for (final emoji in EmojiPicker.hearts) {
|
||||
expect(emojis.contains(emoji), isTrue, reason: 'Missing heart: $emoji');
|
||||
}
|
||||
|
||||
// Should contain objects
|
||||
for (final emoji in EmojiPicker.objects) {
|
||||
expect(emojis.contains(emoji), isTrue, reason: 'Missing object: $emoji');
|
||||
}
|
||||
});
|
||||
|
||||
test('should fit in 1 byte (max 256 emojis)', () {
|
||||
expect(ReactionHelper.reactionEmojis.length, lessThanOrEqualTo(256));
|
||||
});
|
||||
});
|
||||
|
||||
group('emojiToIndex', () {
|
||||
test('should return 2-char hex for valid emoji', () {
|
||||
// First emoji (thumbs up) should be index 0
|
||||
expect(ReactionHelper.emojiToIndex('👍'), equals('00'));
|
||||
|
||||
// Second emoji (heart) should be index 1
|
||||
expect(ReactionHelper.emojiToIndex('❤️'), equals('01'));
|
||||
});
|
||||
|
||||
test('should return null for unknown emoji', () {
|
||||
expect(ReactionHelper.emojiToIndex('🦄'), isNull); // Not in list
|
||||
expect(ReactionHelper.emojiToIndex('invalid'), isNull);
|
||||
expect(ReactionHelper.emojiToIndex(''), isNull);
|
||||
});
|
||||
|
||||
test('should return lowercase hex', () {
|
||||
final index = ReactionHelper.emojiToIndex('👍');
|
||||
expect(index, matches(RegExp(r'^[0-9a-f]{2}$')));
|
||||
});
|
||||
});
|
||||
|
||||
group('indexToEmoji', () {
|
||||
test('should return emoji for valid index', () {
|
||||
expect(ReactionHelper.indexToEmoji('00'), equals('👍'));
|
||||
expect(ReactionHelper.indexToEmoji('01'), equals('❤️'));
|
||||
});
|
||||
|
||||
test('should return null for invalid index', () {
|
||||
expect(ReactionHelper.indexToEmoji('ff'), isNull); // Index 255, out of range
|
||||
expect(ReactionHelper.indexToEmoji('zz'), isNull); // Invalid hex
|
||||
expect(ReactionHelper.indexToEmoji(''), isNull); // Empty string
|
||||
// Note: indexToEmoji parses any valid hex; length validation is done by parseReaction's regex
|
||||
});
|
||||
|
||||
test('should handle case insensitivity', () {
|
||||
// Both uppercase and lowercase should work
|
||||
expect(ReactionHelper.indexToEmoji('0a'), isNotNull);
|
||||
expect(ReactionHelper.indexToEmoji('0A'), isNotNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('emoji round-trip', () {
|
||||
test('all emojis should round-trip correctly', () {
|
||||
for (int i = 0; i < ReactionHelper.reactionEmojis.length; i++) {
|
||||
final emoji = ReactionHelper.reactionEmojis[i];
|
||||
final index = ReactionHelper.emojiToIndex(emoji);
|
||||
expect(index, isNotNull, reason: 'emojiToIndex failed for $emoji');
|
||||
|
||||
final decoded = ReactionHelper.indexToEmoji(index!);
|
||||
expect(decoded, equals(emoji), reason: 'Round-trip failed for $emoji (index $index)');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group('computeReactionHash', () {
|
||||
test('should return 4-char hex hash', () {
|
||||
final hash = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello world');
|
||||
expect(hash, matches(RegExp(r'^[0-9a-f]{4}$')));
|
||||
});
|
||||
|
||||
test('should be deterministic', () {
|
||||
final hash1 = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello');
|
||||
final hash2 = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello');
|
||||
expect(hash1, equals(hash2));
|
||||
});
|
||||
|
||||
test('should differ for different inputs', () {
|
||||
final hash1 = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello');
|
||||
final hash2 = ReactionHelper.computeReactionHash(1234567890, 'Bob', 'Hello');
|
||||
final hash3 = ReactionHelper.computeReactionHash(1234567891, 'Alice', 'Hello');
|
||||
final hash4 = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'World');
|
||||
|
||||
expect(hash1, isNot(equals(hash2))); // Different sender
|
||||
expect(hash1, isNot(equals(hash3))); // Different timestamp
|
||||
expect(hash1, isNot(equals(hash4))); // Different text
|
||||
});
|
||||
|
||||
test('should use first 5 chars of text', () {
|
||||
final hash1 = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello world');
|
||||
final hash2 = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello there');
|
||||
expect(hash1, equals(hash2)); // Same first 5 chars
|
||||
});
|
||||
|
||||
test('should handle short text', () {
|
||||
final hash = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hi');
|
||||
expect(hash, matches(RegExp(r'^[0-9a-f]{4}$')));
|
||||
});
|
||||
|
||||
test('should handle empty text', () {
|
||||
final hash = ReactionHelper.computeReactionHash(1234567890, 'Alice', '');
|
||||
expect(hash, matches(RegExp(r'^[0-9a-f]{4}$')));
|
||||
});
|
||||
});
|
||||
|
||||
group('computeReactionHash with null sender (1:1 chats)', () {
|
||||
test('should return 4-char hex hash', () {
|
||||
final hash = ReactionHelper.computeReactionHash(1234567890, null, 'Hello world');
|
||||
expect(hash, matches(RegExp(r'^[0-9a-f]{4}$')));
|
||||
});
|
||||
|
||||
test('should be deterministic', () {
|
||||
final hash1 = ReactionHelper.computeReactionHash(1234567890, null, 'Hello');
|
||||
final hash2 = ReactionHelper.computeReactionHash(1234567890, null, 'Hello');
|
||||
expect(hash1, equals(hash2));
|
||||
});
|
||||
|
||||
test('should differ for different inputs', () {
|
||||
final hash1 = ReactionHelper.computeReactionHash(1234567890, null, 'Hello');
|
||||
final hash2 = ReactionHelper.computeReactionHash(1234567891, null, 'Hello');
|
||||
final hash3 = ReactionHelper.computeReactionHash(1234567890, null, 'World');
|
||||
|
||||
expect(hash1, isNot(equals(hash2))); // Different timestamp
|
||||
expect(hash1, isNot(equals(hash3))); // Different text
|
||||
});
|
||||
|
||||
test('should differ from hash with sender name', () {
|
||||
// Null sender hash doesn't include sender, so should differ
|
||||
final nullSenderHash = ReactionHelper.computeReactionHash(1234567890, null, 'Hello');
|
||||
final withSenderHash = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello');
|
||||
expect(nullSenderHash, isNot(equals(withSenderHash)));
|
||||
});
|
||||
|
||||
test('1:1 chat flow: sender and receiver compute same hash', () {
|
||||
// Alice sends "Hello" at timestamp 1234567890
|
||||
// Bob receives it and wants to react
|
||||
// Bob computes hash the same way Alice's app will match it
|
||||
const timestamp = 1234567890;
|
||||
const messageText = 'Hello there!';
|
||||
|
||||
// Bob (sender of reaction) computes hash with null sender
|
||||
final bobHash = ReactionHelper.computeReactionHash(timestamp, null, messageText);
|
||||
|
||||
// Alice (receiver of reaction) computes hash for her outgoing message
|
||||
final aliceHash = ReactionHelper.computeReactionHash(timestamp, null, messageText);
|
||||
|
||||
expect(bobHash, equals(aliceHash));
|
||||
});
|
||||
});
|
||||
|
||||
group('parseReaction', () {
|
||||
test('should parse valid reaction format', () {
|
||||
final info = ReactionHelper.parseReaction('r:a1b2:00');
|
||||
expect(info, isNotNull);
|
||||
expect(info!.targetHash, equals('a1b2'));
|
||||
expect(info.emoji, equals('👍'));
|
||||
});
|
||||
|
||||
test('should return null for invalid format', () {
|
||||
expect(ReactionHelper.parseReaction('invalid'), isNull);
|
||||
expect(ReactionHelper.parseReaction('r:abc:00'), isNull); // Hash too short
|
||||
expect(ReactionHelper.parseReaction('r:abcde:00'), isNull); // Hash too long
|
||||
expect(ReactionHelper.parseReaction('r:a1b2:0'), isNull); // Index too short
|
||||
expect(ReactionHelper.parseReaction('r:a1b2:000'), isNull); // Index too long
|
||||
expect(ReactionHelper.parseReaction('R:a1b2:00'), isNull); // Uppercase R
|
||||
expect(ReactionHelper.parseReaction('r:A1B2:00'), isNull); // Uppercase hash
|
||||
expect(ReactionHelper.parseReaction(''), isNull);
|
||||
});
|
||||
|
||||
test('should return null for invalid emoji index', () {
|
||||
// Index ff (255) is likely out of range
|
||||
expect(ReactionHelper.parseReaction('r:a1b2:ff'), isNull);
|
||||
});
|
||||
|
||||
test('should decode emoji correctly', () {
|
||||
// Encode thumbs up and verify decode
|
||||
final index = ReactionHelper.emojiToIndex('👍');
|
||||
final info = ReactionHelper.parseReaction('r:dead:$index');
|
||||
expect(info, isNotNull);
|
||||
expect(info!.emoji, equals('👍'));
|
||||
});
|
||||
});
|
||||
|
||||
group('full reaction flow', () {
|
||||
test('should encode and decode reaction correctly', () {
|
||||
// Simulate sending a reaction
|
||||
const timestamp = 1234567890;
|
||||
const senderName = 'Alice';
|
||||
const messageText = 'Hello world!';
|
||||
const emoji = '🎉';
|
||||
|
||||
// Compute hash (sender side)
|
||||
final hash = ReactionHelper.computeReactionHash(timestamp, senderName, messageText);
|
||||
|
||||
// Encode emoji (sender side)
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji);
|
||||
expect(emojiIndex, isNotNull);
|
||||
|
||||
// Build reaction text (sender side)
|
||||
final reactionText = 'r:$hash:$emojiIndex';
|
||||
|
||||
// Parse reaction (receiver side)
|
||||
final info = ReactionHelper.parseReaction(reactionText);
|
||||
expect(info, isNotNull);
|
||||
expect(info!.targetHash, equals(hash));
|
||||
expect(info.emoji, equals(emoji));
|
||||
|
||||
// Verify receiver can match the hash
|
||||
final receiverHash = ReactionHelper.computeReactionHash(timestamp, senderName, messageText);
|
||||
expect(receiverHash, equals(info.targetHash));
|
||||
});
|
||||
|
||||
test('reaction text should be 9 bytes', () {
|
||||
final hash = ReactionHelper.computeReactionHash(1234567890, 'Alice', 'Hello');
|
||||
final index = ReactionHelper.emojiToIndex('👍')!;
|
||||
final reactionText = 'r:$hash:$index';
|
||||
|
||||
// r: (2) + hash (4) + : (1) + index (2) = 9 bytes
|
||||
expect(reactionText.length, equals(9));
|
||||
});
|
||||
|
||||
test('1:1 chat: Bob reacts to Alice message', () {
|
||||
// Alice sends "Hello" to Bob at timestamp 1234567890
|
||||
const timestamp = 1234567890;
|
||||
const aliceName = 'Alice';
|
||||
const messageText = 'Hello';
|
||||
const emoji = '👍';
|
||||
|
||||
// On Bob's device: message.isOutgoing = false, so senderName = contact.name = Alice
|
||||
final bobSideHash = ReactionHelper.computeReactionHash(timestamp, aliceName, messageText);
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji)!;
|
||||
final reactionText = 'r:$bobSideHash:$emojiIndex';
|
||||
|
||||
// Alice receives the reaction
|
||||
final info = ReactionHelper.parseReaction(reactionText);
|
||||
expect(info, isNotNull);
|
||||
|
||||
// On Alice's device: message.isOutgoing = true, so senderName = selfName = Alice
|
||||
final aliceSideHash = ReactionHelper.computeReactionHash(timestamp, aliceName, messageText);
|
||||
|
||||
// Hashes should match!
|
||||
expect(info!.targetHash, equals(aliceSideHash));
|
||||
expect(info.emoji, equals(emoji));
|
||||
});
|
||||
|
||||
test('1:1 chat: Alice reacts to Bob message', () {
|
||||
// Bob sends "Hi there" to Alice at timestamp 9876543210
|
||||
const timestamp = 9876543210;
|
||||
const bobName = 'Bob';
|
||||
const messageText = 'Hi there';
|
||||
const emoji = '❤️';
|
||||
|
||||
// On Alice's device: message.isOutgoing = false, so senderName = contact.name = Bob
|
||||
final aliceSideHash = ReactionHelper.computeReactionHash(timestamp, bobName, messageText);
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji)!;
|
||||
final reactionText = 'r:$aliceSideHash:$emojiIndex';
|
||||
|
||||
// Bob receives the reaction
|
||||
final info = ReactionHelper.parseReaction(reactionText);
|
||||
expect(info, isNotNull);
|
||||
|
||||
// On Bob's device: message.isOutgoing = true, so senderName = selfName = Bob
|
||||
final bobSideHash = ReactionHelper.computeReactionHash(timestamp, bobName, messageText);
|
||||
|
||||
// Hashes should match!
|
||||
expect(info!.targetHash, equals(bobSideHash));
|
||||
expect(info.emoji, equals(emoji));
|
||||
});
|
||||
|
||||
test('room server: user reacts to message from another user', () {
|
||||
// In a room server, Charlie sends "Hello room" at timestamp 1111111111
|
||||
// Alice wants to react to it
|
||||
const timestamp = 1111111111;
|
||||
const charlieName = 'Charlie';
|
||||
const messageText = 'Hello room';
|
||||
const emoji = '🎉';
|
||||
|
||||
// Alice computes hash including sender name (room servers are multi-user)
|
||||
final aliceHash = ReactionHelper.computeReactionHash(timestamp, charlieName, messageText);
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji)!;
|
||||
final reactionText = 'r:$aliceHash:$emojiIndex';
|
||||
|
||||
// Verify format
|
||||
expect(reactionText.length, equals(9));
|
||||
expect(reactionText, matches(RegExp(r'^r:[0-9a-f]{4}:[0-9a-f]{2}$')));
|
||||
|
||||
// Bob (another user in the room) receives the reaction
|
||||
final info = ReactionHelper.parseReaction(reactionText);
|
||||
expect(info, isNotNull);
|
||||
|
||||
// Bob computes hash for Charlie's message the same way
|
||||
final bobHash = ReactionHelper.computeReactionHash(timestamp, charlieName, messageText);
|
||||
|
||||
// Hashes should match!
|
||||
expect(info!.targetHash, equals(bobHash));
|
||||
expect(info.emoji, equals(emoji));
|
||||
});
|
||||
|
||||
test('room server: hash differs from 1:1 hash for same message content', () {
|
||||
// Same timestamp and text, but room server includes sender name
|
||||
const timestamp = 1234567890;
|
||||
const senderName = 'Dave';
|
||||
const messageText = 'Hello';
|
||||
|
||||
// Room server hash (with sender name)
|
||||
final roomHash = ReactionHelper.computeReactionHash(timestamp, senderName, messageText);
|
||||
|
||||
// 1:1 hash (without sender name)
|
||||
final directHash = ReactionHelper.computeReactionHash(timestamp, null, messageText);
|
||||
|
||||
// They should be different!
|
||||
expect(roomHash, isNot(equals(directHash)));
|
||||
});
|
||||
|
||||
test('room server: different senders produce different hashes', () {
|
||||
// Two users send the exact same message at the same time in a room
|
||||
const timestamp = 1234567890;
|
||||
const messageText = 'Hello';
|
||||
|
||||
final aliceHash = ReactionHelper.computeReactionHash(timestamp, 'Alice', messageText);
|
||||
final bobHash = ReactionHelper.computeReactionHash(timestamp, 'Bob', messageText);
|
||||
|
||||
// Different senders = different hashes (even with same content)
|
||||
expect(aliceHash, isNot(equals(bobHash)));
|
||||
});
|
||||
|
||||
test('room server: self message reaction works', () {
|
||||
// Alice sends "My message" at timestamp 2222222222
|
||||
// Bob wants to react to it
|
||||
const timestamp = 2222222222;
|
||||
const aliceName = 'Alice';
|
||||
const messageText = 'My message';
|
||||
const emoji = '👍';
|
||||
|
||||
// Bob computes hash for Alice's message
|
||||
final bobHash = ReactionHelper.computeReactionHash(timestamp, aliceName, messageText);
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji)!;
|
||||
final reactionText = 'r:$bobHash:$emojiIndex';
|
||||
|
||||
// Alice receives the reaction and matches against her outgoing message
|
||||
final info = ReactionHelper.parseReaction(reactionText);
|
||||
expect(info, isNotNull);
|
||||
|
||||
// Alice computes hash using her selfName
|
||||
final aliceHash = ReactionHelper.computeReactionHash(timestamp, aliceName, messageText);
|
||||
|
||||
// Hashes should match!
|
||||
expect(info!.targetHash, equals(aliceHash));
|
||||
});
|
||||
|
||||
test('channel: same logic as room server', () {
|
||||
// Channel messages also use sender name in hash
|
||||
const timestamp = 3333333333;
|
||||
const senderName = 'Eve';
|
||||
const messageText = 'Channel msg';
|
||||
const emoji = '🔥';
|
||||
|
||||
// Compute hash with sender name
|
||||
final hash = ReactionHelper.computeReactionHash(timestamp, senderName, messageText);
|
||||
final emojiIndex = ReactionHelper.emojiToIndex(emoji)!;
|
||||
final reactionText = 'r:$hash:$emojiIndex';
|
||||
|
||||
// Parse and verify
|
||||
final info = ReactionHelper.parseReaction(reactionText);
|
||||
expect(info, isNotNull);
|
||||
expect(info!.emoji, equals(emoji));
|
||||
|
||||
// Another user computes the same hash
|
||||
final otherUserHash = ReactionHelper.computeReactionHash(timestamp, senderName, messageText);
|
||||
expect(info.targetHash, equals(otherUserHash));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
+4
-1
@@ -466,7 +466,7 @@ def fmt_duration(seconds: float) -> str:
|
||||
|
||||
|
||||
def find_missing_keys(source_data: Dict[str, Any], target_data: Dict[str, Any]) -> List[str]:
|
||||
"""Find keys that are in source but not in target (excluding metadata keys)."""
|
||||
"""Find keys that are in source but not in target, or have empty values (excluding metadata keys)."""
|
||||
missing = []
|
||||
for key in source_data:
|
||||
if key == "@@locale":
|
||||
@@ -475,6 +475,9 @@ def find_missing_keys(source_data: Dict[str, Any], target_data: Dict[str, Any])
|
||||
continue
|
||||
if key not in target_data:
|
||||
missing.append(key)
|
||||
elif isinstance(target_data.get(key), str) and target_data[key].strip() == "":
|
||||
# Also include keys with empty string values
|
||||
missing.append(key)
|
||||
return missing
|
||||
|
||||
|
||||
|
||||
+32
-84
@@ -1,121 +1,69 @@
|
||||
{
|
||||
"bg": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"de": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"sk": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"sl": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"sv": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
"appSettings_languageRu"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
"community_regenerateSecret",
|
||||
"community_regenerateSecretConfirm",
|
||||
"community_regenerate",
|
||||
"community_secretRegenerated",
|
||||
"community_updateSecret",
|
||||
"community_secretUpdated",
|
||||
"community_scanToUpdateSecret"
|
||||
"appSettings_languageRu",
|
||||
"appSettings_languageUk"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <flutter_blue_plus_winrt/flutter_blue_plus_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FlutterBluePlusPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterBluePlusPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_blue_plus_winrt
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user