Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Currently Dicio answers questions about:
- **open**: opens an app on your device - _Open NewPipe_
- **calculator**: evaluates basic calculations - _What is four thousand and two times three minus a million divided by three hundred?_
- **telephone**: view and call contacts - _Call Tom_
- **sms**: send SMS messages to contacts - _Send a text to Jenna saying I got us reservations for dinner_
- **timer**: set, query and cancel timers - _Set a timer for five minutes_
- **current time**: query current time - _What time is it?_
- **navigation**: opens the navigation app at the requested position - _Take me to New York, fifteenth avenue_
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-feature android:name="android.hardware.telephony" android:required="false" />

<!-- the sms skill needs to send text messages -->
<uses-permission android:name="android.permission.SEND_SMS" />

<!-- allowBackup=false because of a critical nasty bug: https://medium.com/p/924c91bafcac -->
<application
android:name=".App"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/eval/SkillHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.stypox.dicio.skills.media.MediaInfo
import org.stypox.dicio.skills.navigation.NavigationInfo
import org.stypox.dicio.skills.open.OpenInfo
import org.stypox.dicio.skills.search.SearchInfo
import org.stypox.dicio.skills.sms.SmsInfo
import org.stypox.dicio.skills.telephone.TelephoneInfo
import org.stypox.dicio.skills.timer.TimerInfo
import org.stypox.dicio.skills.translation.TranslationInfo
Expand All @@ -49,6 +50,7 @@ class SkillHandler @Inject constructor(
CalculatorInfo,
NavigationInfo,
TelephoneInfo,
SmsInfo,
TimerInfo,
CurrentTimeInfo,
MediaInfo,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.stypox.dicio.skills.sms

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.AlwaysBestScore
import org.dicio.skill.skill.AlwaysWorstScore
import org.dicio.skill.skill.InteractionPlan
import org.dicio.skill.skill.Score
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillOutput
import org.dicio.skill.skill.Specificity
import org.stypox.dicio.R
import org.stypox.dicio.io.graphical.Body
import org.stypox.dicio.io.graphical.Headline
import org.stypox.dicio.util.getString

class AskMessageOutput(
private val name: String,
private val number: String
) : SkillOutput {
override fun getSpeechOutput(ctx: SkillContext): String =
ctx.getString(R.string.skill_sms_what_message)

override fun getInteractionPlan(ctx: SkillContext): InteractionPlan {
val messageSkill = object : Skill<String>(SmsInfo, Specificity.HIGH) {
override fun score(
ctx: SkillContext,
input: String
): Pair<Score, String> {
val trimmedInput = input.trim()
return Pair(
if (trimmedInput.isEmpty()) AlwaysWorstScore else AlwaysBestScore,
trimmedInput
)
}

override suspend fun generateOutput(
ctx: SkillContext,
inputData: String
): SkillOutput {
return ConfirmSmsOutput(name, number, inputData)
}
}

return InteractionPlan.StartSubInteraction(
reopenMicrophone = true,
nextSkills = listOf(messageSkill),
)
}

@Composable
override fun GraphicalOutput(ctx: SkillContext) {
Column {
Headline(text = getSpeechOutput(ctx))
Spacer(modifier = Modifier.height(4.dp))
Body(text = "$name ($number)")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.stypox.dicio.skills.sms

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.InteractionPlan
import org.dicio.skill.skill.SkillOutput
import org.stypox.dicio.R
import org.stypox.dicio.io.graphical.Body
import org.stypox.dicio.io.graphical.Headline
import org.stypox.dicio.sentences.Sentences
import org.stypox.dicio.util.RecognizeYesNoSkill
import org.stypox.dicio.util.getString

class ConfirmSmsOutput(
private val name: String,
private val number: String,
private val message: String
) : SkillOutput {
override fun getSpeechOutput(ctx: SkillContext): String =
ctx.getString(R.string.skill_sms_confirm, message, name)

override fun getInteractionPlan(ctx: SkillContext): InteractionPlan {
val yesNoSentences = Sentences.UtilYesNo[ctx.sentencesLanguage]!!
val confirmYesNoSkill = object : RecognizeYesNoSkill(SmsInfo, yesNoSentences) {
override suspend fun generateOutput(
ctx: SkillContext,
inputData: Boolean
): SkillOutput {
return if (inputData) {
SmsSkill.sendSms(number, message)
SentSmsOutput(name, number, message)
} else {
SentSmsOutput(null, null, null)
}
}
}

return InteractionPlan.ReplaceSubInteraction(
reopenMicrophone = true,
nextSkills = listOf(confirmYesNoSkill),
)
}

@Composable
override fun GraphicalOutput(ctx: SkillContext) {
Column {
Headline(text = getSpeechOutput(ctx))
Spacer(modifier = Modifier.height(4.dp))
Body(text = "$name ($number)")
}
}
}
44 changes: 44 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/skills/sms/SentSmsOutput.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.stypox.dicio.skills.sms

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.SkillOutput
import org.stypox.dicio.R
import org.stypox.dicio.io.graphical.Body
import org.stypox.dicio.io.graphical.Headline
import org.stypox.dicio.util.getString

class SentSmsOutput(
private val name: String?,
private val number: String?,
private val message: String?
) : SkillOutput {
override fun getSpeechOutput(ctx: SkillContext): String = if (name == null) {
ctx.getString(R.string.skill_sms_not_sending)
} else {
"" // do not speak anything since the message was sent
}

@Composable
override fun GraphicalOutput(ctx: SkillContext) {
if (name == null) {
Headline(text = stringResource(R.string.skill_sms_not_sending))
} else {
Column {
Headline(text = stringResource(R.string.skill_sms_sent, name))
Spacer(modifier = Modifier.height(4.dp))
Body(text = number ?: "")
if (message != null) {
Spacer(modifier = Modifier.height(8.dp))
Body(text = "\"$message\"")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.stypox.dicio.skills.sms

import org.dicio.numbers.unit.Number
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.AlwaysBestScore
import org.dicio.skill.skill.AlwaysWorstScore
import org.dicio.skill.skill.Score
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillOutput
import org.dicio.skill.skill.Specificity

class SmsContactChooserIndex internal constructor(
private val contacts: List<Pair<String, String>>,
private val messageText: String?
) : Skill<Int>(SmsInfo, Specificity.HIGH) {

override fun score(
ctx: SkillContext,
input: String
): Pair<Score, Int> {
val index = ctx.parserFormatter!!
.extractNumber(input)
.preferOrdinal(true)
.mixedWithText
.asSequence()
.filter { obj -> (obj as? Number)?.isInteger == true }
.map { obj -> (obj as Number).integerValue().toInt() }
.firstOrNull() ?: 0
return Pair(
if (index <= 0 || index > contacts.size) AlwaysWorstScore else AlwaysBestScore,
index
)
}

override suspend fun generateOutput(ctx: SkillContext, inputData: Int): SkillOutput {
if (inputData > 0 && inputData <= contacts.size) {
val contact = contacts[inputData - 1]
return if (messageText == null) {
AskMessageOutput(contact.first, contact.second)
} else {
ConfirmSmsOutput(contact.first, contact.second, messageText)
}
} else {
// impossible situation
return SentSmsOutput(null, null, null)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.stypox.dicio.skills.sms

import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.AlwaysBestScore
import org.dicio.skill.skill.AlwaysWorstScore
import org.dicio.skill.skill.Score
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillOutput
import org.dicio.skill.skill.Specificity
import org.stypox.dicio.util.StringUtils

class SmsContactChooserName internal constructor(
private val contacts: List<Pair<String, String>>,
private val messageText: String?
) : Skill<Pair<String, String>?>(SmsInfo, Specificity.LOW) {

override fun score(
ctx: SkillContext,
input: String
): Pair<Score, Pair<String, String>?> {
val trimmedInput = input.trim { it <= ' ' }

val bestContact = contacts
.map { nameNumberPair ->
Pair(
nameNumberPair,
StringUtils.contactStringDistance(trimmedInput, nameNumberPair.first)
)
}
.filter { pair -> pair.second < -7 }
.minByOrNull { a -> a.second }
?.first

return Pair(
if (bestContact == null) AlwaysWorstScore else AlwaysBestScore,
bestContact
)
}

override suspend fun generateOutput(ctx: SkillContext, inputData: Pair<String, String>?): SkillOutput {
return inputData?.let {
if (messageText == null) {
AskMessageOutput(it.first, it.second)
} else {
ConfirmSmsOutput(it.first, it.second, messageText)
}
}
// impossible situation
?: SentSmsOutput(null, null, null)
}
}
39 changes: 39 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/skills/sms/SmsInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.stypox.dicio.skills.sms

import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Message
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.Permission
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillInfo
import org.stypox.dicio.R
import org.stypox.dicio.sentences.Sentences
import org.stypox.dicio.util.PERMISSION_READ_CONTACTS
import org.stypox.dicio.util.PERMISSION_SEND_SMS

object SmsInfo : SkillInfo("sms") {
override fun name(context: Context) =
context.getString(R.string.skill_name_sms)

override fun sentenceExample(context: Context) =
context.getString(R.string.skill_sentence_example_sms)

@Composable
override fun icon() =
rememberVectorPainter(Icons.Default.Message)

override val neededPermissions: List<Permission>
= listOf(PERMISSION_READ_CONTACTS, PERMISSION_SEND_SMS)

override fun isAvailable(ctx: SkillContext): Boolean {
return Sentences.Sms[ctx.sentencesLanguage] != null &&
Sentences.UtilYesNo[ctx.sentencesLanguage] != null
}

override fun build(ctx: SkillContext): Skill<*> {
return SmsSkill(SmsInfo, Sentences.Sms[ctx.sentencesLanguage]!!)
}
}
Loading