feat(voice): add direct speech recognition, system ROLE_NOTES, voice shortcuts, and change app label to Avtex
This commit is contained in:
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId = "com.example.avtex"
|
applicationId = "com.example.avtex"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 3
|
versionCode = 4
|
||||||
versionName = "1.2"
|
versionName = "1.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -49,6 +49,11 @@ android {
|
|||||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lint {
|
||||||
|
abortOnError = false
|
||||||
|
checkReleaseBuilds = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:showWhenLocked="true"
|
||||||
|
android:turnScreenOn="true"
|
||||||
android:icon="@mipmap/ic_launcher_avtex"
|
android:icon="@mipmap/ic_launcher_avtex"
|
||||||
android:roundIcon="@mipmap/ic_launcher_avtex_round">
|
android:roundIcon="@mipmap/ic_launcher_avtex_round">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -52,6 +54,24 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Google Voice Actions Note Creation -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.gms.actions.CREATE_NOTE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.gms.actions.CREATE_NOTE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Android Text Selection Process Action (выделение текста в любом месте -> отправить в Avtex) -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -1,22 +1,52 @@
|
|||||||
package com.example.avtex
|
package com.example.avtex
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.role.RoleManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.speech.RecognizerIntent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.example.avtex.theme.AvtexTheme
|
import com.example.avtex.theme.AvtexTheme
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
val LocalVoiceInputLauncher = compositionLocalOf<(() -> Unit)?> { null }
|
||||||
|
val LocalRoleNotesRequester = compositionLocalOf<(() -> Unit)?> { null }
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
private val speechRecognizerLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
|
||||||
|
val matches = result.data?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
|
||||||
|
val spokenText = matches?.firstOrNull()
|
||||||
|
if (!spokenText.isNullOrBlank()) {
|
||||||
|
routeAndSaveNote(spokenText, preferredList = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val roleRequestLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
Toast.makeText(this, "Avtex назначен приложением заметок по умолчанию!", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
handleIncomingIntent(intent)
|
handleIncomingIntent(intent)
|
||||||
@@ -24,6 +54,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
AvtexTheme {
|
AvtexTheme {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalVoiceInputLauncher provides { launchVoiceInput() },
|
||||||
|
LocalRoleNotesRequester provides { requestDefaultNotesRole() }
|
||||||
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
@@ -33,12 +67,48 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
handleIncomingIntent(intent)
|
handleIncomingIntent(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun launchVoiceInput() {
|
||||||
|
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
|
||||||
|
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
|
||||||
|
putExtra(RecognizerIntent.EXTRA_LANGUAGE, "ru-RU")
|
||||||
|
putExtra(RecognizerIntent.EXTRA_PROMPT, "Скажите заметку, задачу или фильм...")
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
speechRecognizerLauncher.launch(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Speech recognizer not available", e)
|
||||||
|
Toast.makeText(this, "Голосовой ввод недоступен на этом устройстве", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestDefaultNotesRole() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
try {
|
||||||
|
val roleManager = getSystemService(RoleManager::class.java)
|
||||||
|
if (roleManager != null && roleManager.isRoleAvailable(RoleManager.ROLE_NOTES)) {
|
||||||
|
val roleIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_NOTES)
|
||||||
|
roleRequestLauncher.launch(roleIntent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to request ROLE_NOTES", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val settingsIntent = Intent(android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
|
||||||
|
startActivity(settingsIntent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Toast.makeText(this, "Откройте: Настройки -> Приложения -> Приложения по умолчанию", Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleIncomingIntent(intent: Intent?) {
|
private fun handleIncomingIntent(intent: Intent?) {
|
||||||
if (intent == null) return
|
if (intent == null) return
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
@@ -54,10 +124,37 @@ class MainActivity : ComponentActivity() {
|
|||||||
val fullText = if (!title.isNullOrBlank() && title != text) "$title: $text" else text
|
val fullText = if (!title.isNullOrBlank() && title != text) "$title: $text" else text
|
||||||
routeAndSaveNote(fullText, preferredList = null)
|
routeAndSaveNote(fullText, preferredList = null)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
launchVoiceInput()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Android Share Sheet Action (android.intent.action.SEND with text/plain)
|
// 2. Google Voice Actions (com.google.android.gms.actions.CREATE_NOTE)
|
||||||
|
if (action == "com.google.android.gms.actions.CREATE_NOTE") {
|
||||||
|
val text = intent.getStringExtra("com.google.android.gms.actions.extra.TEXT")
|
||||||
|
?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
val name = intent.getStringExtra("com.google.android.gms.actions.extra.NAME")
|
||||||
|
if (!text.isNullOrBlank()) {
|
||||||
|
val fullText = if (!name.isNullOrBlank() && name != text) "$name: $text" else text
|
||||||
|
routeAndSaveNote(fullText, preferredList = null)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
launchVoiceInput()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Android Text Selection (ACTION_PROCESS_TEXT)
|
||||||
|
if (action == Intent.ACTION_PROCESS_TEXT) {
|
||||||
|
val text = intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)?.toString()
|
||||||
|
if (!text.isNullOrBlank()) {
|
||||||
|
routeAndSaveNote(text, preferredList = null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Android Share Sheet Action (android.intent.action.SEND with text/plain)
|
||||||
if (action == Intent.ACTION_SEND && intent.type?.startsWith("text/") == true) {
|
if (action == Intent.ACTION_SEND && intent.type?.startsWith("text/") == true) {
|
||||||
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||||
@@ -68,10 +165,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Deep Link Action (xflow://...)
|
// 5. Deep Link Action (xflow://...)
|
||||||
val data = intent.data
|
val data = intent.data
|
||||||
if (data != null && data.scheme == "xflow") {
|
if (data != null && data.scheme == "xflow") {
|
||||||
val path = data.path ?: data.host ?: ""
|
val host = data.host ?: ""
|
||||||
|
val path = data.path ?: ""
|
||||||
|
if (host == "voice" || path.contains("record")) {
|
||||||
|
launchVoiceInput()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val listName = data.getQueryParameter("list_name")
|
val listName = data.getQueryParameter("list_name")
|
||||||
?: data.getQueryParameter("name")
|
?: data.getQueryParameter("name")
|
||||||
?: data.getQueryParameter("list")
|
?: data.getQueryParameter("list")
|
||||||
@@ -88,7 +191,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Google Assistant / Gemini Built-in Intent Extras
|
// 6. Google Assistant / Gemini Built-in Intent Extras
|
||||||
val extras = intent.extras
|
val extras = intent.extras
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
val itemName = extras.getString("itemListElement.name")
|
val itemName = extras.getString("itemListElement.name")
|
||||||
@@ -124,13 +227,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
"✅ XFlow: Записано в «$targetList»:\n$cleanText",
|
"✅ Avtex: Записано в «$targetList»:\n$cleanText",
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
|
|
||||||
AvtexApplication.instance.settingsRepository.addLog(
|
AvtexApplication.instance.settingsRepository.addLog(
|
||||||
tag = "Assistant",
|
tag = "Assistant",
|
||||||
message = "Заметка через ассистент: \"$cleanText\" в список \"$targetList\"",
|
message = "Голосовая заметка: \"$cleanText\" в список \"$targetList\"",
|
||||||
isImportant = true
|
isImportant = true
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ import androidx.compose.material.icons.filled.ExpandMore
|
|||||||
import androidx.compose.material.icons.filled.Mic
|
import androidx.compose.material.icons.filled.Mic
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.material.icons.filled.Terminal
|
import androidx.compose.material.icons.filled.Terminal
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import com.example.avtex.LocalVoiceInputLauncher
|
||||||
|
import com.example.avtex.LocalRoleNotesRequester
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
@@ -94,6 +98,7 @@ fun ListsScreen(modifier: Modifier = Modifier) {
|
|||||||
var selectedCategory by remember { mutableStateOf("all") }
|
var selectedCategory by remember { mutableStateOf("all") }
|
||||||
var newItemText by remember { mutableStateOf("") }
|
var newItemText by remember { mutableStateOf("") }
|
||||||
var showAssistantTester by remember { mutableStateOf(false) }
|
var showAssistantTester by remember { mutableStateOf(false) }
|
||||||
|
val voiceLauncher = LocalVoiceInputLauncher.current
|
||||||
|
|
||||||
val filteredLists = remember(lists, selectedCategory) {
|
val filteredLists = remember(lists, selectedCategory) {
|
||||||
if (selectedCategory == "all") lists
|
if (selectedCategory == "all") lists
|
||||||
@@ -129,6 +134,29 @@ fun ListsScreen(modifier: Modifier = Modifier) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
// Direct Voice Button
|
||||||
|
Button(
|
||||||
|
onClick = { voiceLauncher?.invoke() },
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = NeonViolet),
|
||||||
|
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 6.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Mic,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "Голос",
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Button to toggle Assistant Tester
|
// Button to toggle Assistant Tester
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { showAssistantTester = !showAssistantTester },
|
onClick = { showAssistantTester = !showAssistantTester },
|
||||||
@@ -156,6 +184,7 @@ fun ListsScreen(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Interactive Assistant Simulator Card
|
// 2. Interactive Assistant Simulator Card
|
||||||
item {
|
item {
|
||||||
@@ -307,6 +336,8 @@ fun ListsScreen(modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AssistantTesterCard(context: Context) {
|
private fun AssistantTesterCard(context: Context) {
|
||||||
|
val voiceLauncher = LocalVoiceInputLauncher.current
|
||||||
|
val roleRequester = LocalRoleNotesRequester.current
|
||||||
var customVoiceQuery by remember { mutableStateOf("") }
|
var customVoiceQuery by remember { mutableStateOf("") }
|
||||||
var selectedPreset by remember { mutableStateOf("") }
|
var selectedPreset by remember { mutableStateOf("") }
|
||||||
|
|
||||||
@@ -371,6 +402,53 @@ private fun AssistantTesterCard(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Direct Voice Action & System Role
|
||||||
|
Button(
|
||||||
|
onClick = { voiceLauncher?.invoke() },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = NeonViolet)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Mic, contentDescription = null, tint = Color.White)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text("🎤 Сказать голосом прямо сейчас", fontWeight = FontWeight.Bold, color = Color.White)
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { roleRequester?.invoke() },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
border = androidx.compose.foundation.BorderStroke(1.dp, NeonViolet.copy(alpha = 0.5f))
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Settings, contentDescription = null, tint = NeonViolet, modifier = Modifier.size(16.dp))
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text("⚙️ Сделать Avtex заметками по умолчанию", fontSize = 12.sp, color = NeonViolet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explanatory note about Google Keep
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.background(ObsidianSurface)
|
||||||
|
.border(1.dp, GlassBorder, RoundedCornerShape(10.dp))
|
||||||
|
.padding(10.dp)
|
||||||
|
) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Icon(Icons.Default.Info, contentDescription = null, tint = SolarAmber, modifier = Modifier.size(14.dp))
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text("Почему Ассистент отправляет в Google Keep?", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = SolarAmber)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "Google зафиксировал Keep как облачного провайдера заметок. Чтобы переключить:\n• Откройте: Настройки -> Google -> Google Ассистент -> «Заметки и списки» -> выберите «Не синхронизировать».\n• Либо произносите явно: «Окей Google, открой Avtex и запиши...».\n• Либо нажмите кнопку «Сказать голосом» выше или зажмите иконку Avtex на рабочем столе!",
|
||||||
|
fontSize = 10.sp,
|
||||||
|
color = TextSecondary,
|
||||||
|
lineHeight = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Быстрые сценарии тестирования (нажмите для проверки):",
|
text = "Быстрые сценарии тестирования (нажмите для проверки):",
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">XFlow</string>
|
<string name="app_name">Avtex</string>
|
||||||
|
<string name="shortcut_voice_note">Голосовая заметка</string>
|
||||||
|
<string name="shortcut_voice_note_desc">Быстрая запись заметки голосом в Avtex</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<!-- Шорткат по долгому нажатию на иконку: Голосовая заметка -->
|
||||||
|
<shortcut
|
||||||
|
android:shortcutId="voice_note"
|
||||||
|
android:enabled="true"
|
||||||
|
android:icon="@mipmap/ic_launcher_avtex"
|
||||||
|
android:shortcutShortLabel="@string/shortcut_voice_note"
|
||||||
|
android:shortcutLongLabel="@string/shortcut_voice_note_desc">
|
||||||
|
<intent
|
||||||
|
android:action="android.intent.action.VIEW"
|
||||||
|
android:targetPackage="com.example.avtex"
|
||||||
|
android:targetClass="com.example.avtex.MainActivity"
|
||||||
|
android:data="xflow://voice/record" />
|
||||||
|
<categories android:name="android.shortcut.conversation" />
|
||||||
|
</shortcut>
|
||||||
|
|
||||||
<!-- Создание заметки через Gemini / Google Assistant ("Сделай заметку...") -->
|
<!-- Создание заметки через Gemini / Google Assistant ("Сделай заметку...") -->
|
||||||
<capability android:name="actions.intent.CREATE_NOTE">
|
<capability android:name="actions.intent.CREATE_NOTE">
|
||||||
<intent
|
<intent
|
||||||
|
|||||||
Reference in New Issue
Block a user