feat(wallet): /pay slash command parser and composer integration (Task 5)

Implements Task 5 of Phase 1:

New files:
- ParsedPayCommand.kt: Sealed interface for parse results
  - WithAddressRecipient: Pay to Cardano address
  - WithMatrixRecipient: Pay to Matrix user (requires lookup)
  - AmountOnly: Amount specified, prompt for recipient
  - Empty: Open payment flow with no prefilled data
  - ParseError: Parse error with human-readable reason

- SlashCommandParser.kt: Full /pay command parser
  - Handles: /pay, /pay 10, /pay 10 ADA, /pay 10 tADA
  - Matrix recipients: /pay 10 ADA @user:server
  - Cardano addresses: /pay 10 ADA addr1...
  - Validates amounts (decimal support, max supply check)
  - Validates addresses (prefix, length, network match)
  - Comprehensive error messages

- SlashCommandParserTest.kt: 40+ unit tests covering all patterns

Modified files:
- ResolvedSuggestion.kt: Added Command type for slash commands
- SuggestionsProcessor.kt: /pay shows as autocomplete suggestion
- MarkdownTextEditorState.kt: Command insertion in text editor
- MessageComposerPresenter.kt: Command handling in InsertSuggestion

Note: MessageComposerPresenter sendMessage interception deferred to
Task 6 (requires PaymentFlowPresenter for navigation).
This commit is contained in:
Kayos 2026-03-27 10:38:21 -07:00
parent 880454847e
commit db4c262b27
18 changed files with 1935 additions and 6 deletions

View file

@ -32,4 +32,12 @@ sealed interface ResolvedSuggestion {
size = size,
)
}
/**
* A slash command suggestion (e.g., /pay).
*/
data class Command(
val command: String,
val description: String,
) : ResolvedSuggestion
}

View file

@ -77,6 +77,15 @@ class MarkdownTextEditorState(
this.text.update(currentText, true)
this.selection = IntRange(end + 1, end + 1)
}
is ResolvedSuggestion.Command -> {
// Insert the command text with a trailing space
val commandWithSpace = "${resolvedSuggestion.command} "
val currentText = SpannableStringBuilder(text.value())
currentText.replace(suggestion.start, suggestion.end, commandWithSpace)
val newCursorPosition = suggestion.start + commandWithSpace.length
this.text.update(currentText, true)
this.selection = IntRange(newCursorPosition, newCursorPosition)
}
}
}