feat: Add support for hash and private channels with corresponding UI updates

- Introduced new localization strings for channel types and their descriptions in Italian, German, Spanish, French, Croatian, Slovenian, and English.
- Updated the `AppProvider` to handle channel info reception, including deletion of channels and updating the UI accordingly.
- Enhanced `ChannelsProvider` to support channel removal and notify listeners.
- Modified `ConnectionProvider` to include methods for checking empty channel slots and deleting channels.
- Updated BLE service methods to handle channel deletion and verification.
- Improved the `AddChannelDialog` to differentiate between hash and private channels, including dynamic UI elements based on channel type.
- Added delete functionality for channels in the `ContactTile` widget with confirmation dialogs.
- Adjusted permission request dialog behavior to allow dismissing by tapping outside.
This commit is contained in:
Janez T
2025-11-14 11:20:16 +01:00
parent 21daa83c1a
commit a0f096cc4f
26 changed files with 704 additions and 121 deletions

View File

@@ -1043,7 +1043,19 @@ class BleResponseHandler {
final flags = info['flags'] as int?;
debugPrint(' ✅ [ChannelInfo] Channel $channelIdx: "$channelName"');
onChannelInfoReceived?.call(channelIdx, channelName, secret, flags);
debugPrint(' Name length: ${channelName.length}');
debugPrint(' Name bytes: ${channelName.codeUnits.map((c) => c.toRadixString(16).padLeft(2, '0')).join(' ')}');
debugPrint(' Secret: ${secret.map((b) => b.toRadixString(16).padLeft(2, '0')).join('')}');
debugPrint(' isEmpty: ${channelName.isEmpty}');
debugPrint(' Callback exists: ${onChannelInfoReceived != null}');
if (onChannelInfoReceived != null) {
debugPrint(' 🔔 Calling onChannelInfoReceived callback...');
onChannelInfoReceived!(channelIdx, channelName, secret, flags);
debugPrint(' ✅ Callback completed');
} else {
debugPrint(' ⚠️ No callback registered!');
}
}
} catch (e) {
debugPrint(' ❌ [ChannelInfo] Parsing error: $e');

View File

@@ -609,6 +609,9 @@ class MeshCoreBleService {
///
/// The secret must be exactly 16 bytes (128-bit encryption key).
/// For the default public channel (channel 0), use [MeshCoreConstants.defaultPublicChannelSecret].
///
/// Note: Some firmware versions don't send ACK for SET_CHANNEL, so we use
/// fire-and-forget and then verify with GET_CHANNEL.
Future<void> setChannel({
required int channelIdx,
required String channelName,
@@ -618,23 +621,58 @@ class MeshCoreBleService {
debugPrint(' Channel index: $channelIdx');
debugPrint(' Channel name: $channelName');
debugPrint(' Secret length: ${secret.length} bytes');
debugPrint(' Secret hex: ${secret.map((b) => b.toRadixString(16).padLeft(2, '0')).join('')}');
await _commandSender.writeDataAndWaitForAck(
FrameBuilder.buildSetChannel(
channelIdx: channelIdx,
channelName: channelName,
secret: secret,
),
// Send SET_CHANNEL command (fire-and-forget, no ACK expected)
final setChannelData = FrameBuilder.buildSetChannel(
channelIdx: channelIdx,
channelName: channelName,
secret: secret,
);
debugPrint('✅ [BLE] CMD_SET_CHANNEL sent successfully');
debugPrint(' SET_CHANNEL data (${setChannelData.length} bytes): ${setChannelData.map((b) => b.toRadixString(16).padLeft(2, '0')).join(' ')}');
await _commandSender.writeData(setChannelData);
debugPrint('✅ [BLE] CMD_SET_CHANNEL sent');
// Wait a bit for the device to process
await Future.delayed(const Duration(milliseconds: 200));
// Verify the channel was set by reading it back
debugPrint('🔍 [BLE] Verifying channel was set...');
await getChannel(channelIdx);
}
/// Sync all channels from the device (typically 0-39)
/// This queries each channel to get its name and metadata
Future<void> syncAllChannels({int maxChannels = 40}) async {
debugPrint('📻 [Service] Syncing channels (0-${maxChannels - 1})...');
/// Delete a channel by clearing its slot
///
/// This removes the channel from the device by setting it to an empty name and zeroed secret.
/// The channel slot becomes available for reuse.
///
/// Note: Channel 0 (public channel) cannot be deleted.
Future<void> deleteChannel(int channelIdx) async {
if (channelIdx == 0) {
throw ArgumentError('Cannot delete channel 0 (public channel)');
}
for (int i = 0; i < maxChannels; i++) {
debugPrint('🗑️ [BLE] Deleting channel $channelIdx...');
// Clear channel by setting empty name and zeroed secret
await setChannel(
channelIdx: channelIdx,
channelName: '',
secret: List.filled(16, 0),
);
debugPrint('✅ [BLE] Channel $channelIdx deleted');
}
/// Sync all channels from the device (channels 1-39)
/// Skips channel 0 (public channel) which is implicit and not stored on device
Future<void> syncAllChannels({int maxChannels = 40}) async {
debugPrint('📻 [Service] Syncing channels (1-${maxChannels - 1})...');
// Start from 1 to skip channel 0 (public channel)
// Channel 0 is implicit and handled separately via configurePublicChannel()
for (int i = 1; i < maxChannels; i++) {
await getChannel(i);
// Small delay to avoid overwhelming the device
await Future.delayed(const Duration(milliseconds: 50));

View File

@@ -390,16 +390,21 @@ class FrameParser {
/// Parse ChannelInfo response
static Map<String, dynamic> parseChannelInfo(BufferReader reader) {
// Format: [channel_idx(1)][name(32)][secret(16)][flags(1)]
// Minimum: 1 + 32 + 16 + 1 = 50 bytes
if (reader.remainingBytesCount < 50) {
// Format: [channel_idx(1)][name(32)][secret(16)][flags(1)?]
// Minimum: 1 + 32 + 16 = 49 bytes (flags is optional)
if (reader.remainingBytesCount < 49) {
return {};
}
final channelIdx = reader.readByte();
final channelName = reader.readCString(32);
final secret = reader.readBytes(16);
final flags = reader.readByte();
// Flags field is optional (some firmware versions don't include it)
int? flags;
if (reader.remainingBytesCount >= 1) {
flags = reader.readByte();
}
return {
'channelIdx': channelIdx,