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
37 changes: 37 additions & 0 deletions app/src/main/java/ee/ioc/phon/android/speak/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ee.ioc.phon.android.speak

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import ee.ioc.phon.android.speak.model.ComboDao
import ee.ioc.phon.android.speak.model.ComboListDao
import ee.ioc.phon.android.speak.model.ServiceDao
import ee.ioc.phon.android.speak.model.ComboRepository
import ee.ioc.phon.android.speak.ui.AppNavHost

// TODO: Replace with your actual Room database class
import androidx.room.Room
import ee.ioc.phon.android.speak.model.ComboDatabase

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize Room database (replace ComboDatabase::class.java with your actual database class)
val db = Room.databaseBuilder(
applicationContext,
ComboDatabase::class.java,
"combo-db"
).build()
val repository = ComboRepository(
db.serviceDao(),
db.comboDao(),
db.comboListDao()
)
setContent {
MaterialTheme {
AppNavHost(repository = repository)
}
}
}
}
54 changes: 54 additions & 0 deletions app/src/main/java/ee/ioc/phon/android/speak/model/ComboDaos.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ee.ioc.phon.android.speak.model

import androidx.room.*

@Dao
interface ServiceDao {
@Query("SELECT * FROM services")
suspend fun getAll(): List<ServiceEntity>

@Query("SELECT * FROM services WHERE id = :id")
suspend fun getById(id: String): ServiceEntity?

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(service: ServiceEntity)

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(services: List<ServiceEntity>)

@Delete
suspend fun delete(service: ServiceEntity)
}

@Dao
interface ComboDao {
@Query("SELECT * FROM combos WHERE comboListType = :type ORDER BY comboId")
suspend fun getByListType(type: String): List<ComboEntity>

@Query("SELECT * FROM combos WHERE comboId = :comboId")
suspend fun getById(comboId: Long): ComboEntity?

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(combo: ComboEntity): Long

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(combos: List<ComboEntity>)

@Update
suspend fun update(combo: ComboEntity)

@Delete
suspend fun delete(combo: ComboEntity)
}

@Dao
interface ComboListDao {
@Query("SELECT * FROM combo_lists WHERE type = :type")
suspend fun getByType(type: String): ComboListEntity?

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(comboList: ComboListEntity)

@Update
suspend fun update(comboList: ComboListEntity)
}
14 changes: 14 additions & 0 deletions app/src/main/java/ee/ioc/phon/android/speak/model/ComboDatabase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ee.ioc.phon.android.speak.model

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(
entities = [ServiceEntity::class, ComboEntity::class, ComboListEntity::class],
version = 1
)
abstract class ComboDatabase : RoomDatabase() {
abstract fun serviceDao(): ServiceDao
abstract fun comboDao(): ComboDao
abstract fun comboListDao(): ComboListDao
}
31 changes: 31 additions & 0 deletions app/src/main/java/ee/ioc/phon/android/speak/model/ComboEntities.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ee.ioc.phon.android.speak.model

import androidx.room.*

@Entity(tableName = "services")
data class ServiceEntity(
@PrimaryKey val id: String, // ComponentName flattened string
val name: String,
val description: String?,
val iconRes: String?, // Could be a URI or resource name
val settingsActivity: String?
)

@Entity(tableName = "combos")
data class ComboEntity(
@PrimaryKey(autoGenerate = true) val comboId: Long = 0,
val serviceId: String, // Foreign key to ServiceEntity
val shortLabel: String,
val longLabel: String,
val tinyLabel: String,
val enabled: Boolean = true,
val inputLanguage: String?,
val extras: String?, // JSON-encoded map of RecognizerIntent and service-specific extras
val comboListType: String // "IME" or "PANEL"
)

@Entity(tableName = "combo_lists")
data class ComboListEntity(
@PrimaryKey val type: String, // "IME" or "PANEL"
val order: String // JSON-encoded list of comboIds in order
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ee.ioc.phon.android.speak.model

import kotlinx.coroutines.flow.Flow

class ComboRepository(
private val serviceDao: ServiceDao,
private val comboDao: ComboDao,
private val comboListDao: ComboListDao
) {
// Service operations
suspend fun getAllServices(): List<ServiceEntity> = serviceDao.getAll()
suspend fun getServiceById(id: String): ServiceEntity? = serviceDao.getById(id)
suspend fun insertService(service: ServiceEntity) = serviceDao.insert(service)
suspend fun insertServices(services: List<ServiceEntity>) = serviceDao.insertAll(services)
suspend fun deleteService(service: ServiceEntity) = serviceDao.delete(service)

// Combo operations
suspend fun getCombosByListType(type: String): List<ComboEntity> = comboDao.getByListType(type)
suspend fun getComboById(comboId: Long): ComboEntity? = comboDao.getById(comboId)
suspend fun insertCombo(combo: ComboEntity): Long = comboDao.insert(combo)
suspend fun insertCombos(combos: List<ComboEntity>) = comboDao.insertAll(combos)
suspend fun updateCombo(combo: ComboEntity) = comboDao.update(combo)
suspend fun deleteCombo(combo: ComboEntity) = comboDao.delete(combo)

// ComboList operations
suspend fun getComboListByType(type: String): ComboListEntity? = comboListDao.getByType(type)
suspend fun insertComboList(comboList: ComboListEntity) = comboListDao.insert(comboList)
suspend fun updateComboList(comboList: ComboListEntity) = comboListDao.update(comboList)
}
61 changes: 61 additions & 0 deletions app/src/main/java/ee/ioc/phon/android/speak/ui/AppNavHost.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package ee.ioc.phon.android.speak.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import ee.ioc.phon.android.speak.model.ComboRepository
import ee.ioc.phon.android.speak.viewmodel.ComboDetailsViewModel
import ee.ioc.phon.android.speak.viewmodel.ComboListViewModel
import ee.ioc.phon.android.speak.viewmodel.ServicePickerViewModel

sealed class Screen(val route: String) {
object ComboList : Screen("comboList")
object ServicePicker : Screen("servicePicker")
data class ComboDetails(val comboId: Long) : Screen("comboDetails/{comboId}") {
companion object {
fun createRoute(comboId: Long) = "comboDetails/$comboId"
}
}
}

@Composable
fun AppNavHost(
repository: ComboRepository,
startDestination: String = Screen.ComboList.route
) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = startDestination) {
composable(Screen.ComboList.route) {
val viewModel = remember { ComboListViewModel(repository, "IME") }
ComboListScreen(
viewModel = viewModel,
onComboClick = { combo -> navController.navigate(Screen.ComboDetails.createRoute(combo.comboId)) },
onCloneCombo = { /* TODO: implement clone */ },
onRemoveCombo = { combo -> viewModel.removeCombo(combo) },
onAddCombo = { navController.navigate(Screen.ServicePicker.route) },
onLoadCombos = { /* TODO: implement load */ },
onSaveCombos = { /* TODO: implement save */ }
)
}
composable(Screen.ServicePicker.route) {
val viewModel = remember { ServicePickerViewModel(repository) }
ServicePickerScreen(
viewModel = viewModel,
onServiceSelected = { /* TODO: handle service selection */ },
onConfigureService = { /* TODO: handle service config */ }
)
}
composable("comboDetails/{comboId}") { backStackEntry ->
val comboId = backStackEntry.arguments?.getString("comboId")?.toLongOrNull() ?: return@composable
val viewModel = remember { ComboDetailsViewModel(repository, comboId) }
ComboDetailsScreen(
viewModel = viewModel,
onSave = { /* TODO: handle save */ },
onConfigureService = { /* TODO: handle config */ }
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package ee.ioc.phon.android.speak.ui

import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import ee.ioc.phon.android.speak.model.ComboEntity
import ee.ioc.phon.android.speak.viewmodel.ComboDetailsViewModel

@Composable
fun ComboDetailsScreen(
viewModel: ComboDetailsViewModel,
onSave: (ComboEntity) -> Unit,
onConfigureService: () -> Unit
) {
val comboState = viewModel.combo.collectAsState()
val combo = comboState.value ?: return

var shortLabel by remember { mutableStateOf(TextFieldValue(combo.shortLabel)) }
var longLabel by remember { mutableStateOf(TextFieldValue(combo.longLabel)) }
var tinyLabel by remember { mutableStateOf(TextFieldValue(combo.tinyLabel)) }
var inputLanguage by remember { mutableStateOf(TextFieldValue(combo.inputLanguage ?: "")) }
var enabled by remember { mutableStateOf(combo.enabled) }
var extras by remember { mutableStateOf(TextFieldValue(combo.extras ?: "")) }

Scaffold(
topBar = {
TopAppBar(title = { Text("Combo Details") }, actions = {
IconButton(onClick = onConfigureService) {
Icon(Icons.Default.Settings, contentDescription = "Configure Service")
}
})
},
floatingActionButton = {
FloatingActionButton(onClick = {
val updatedCombo = combo.copy(
shortLabel = shortLabel.text,
longLabel = longLabel.text,
tinyLabel = tinyLabel.text,
inputLanguage = inputLanguage.text,
enabled = enabled,
extras = extras.text
)
onSave(updatedCombo)
}) {
Icon(Icons.Default.Settings, contentDescription = "Save")
}
}
) { padding ->
Column(modifier = Modifier.padding(padding).padding(16.dp)) {
OutlinedTextField(
value = shortLabel,
onValueChange = { shortLabel = it },
label = { Text("Short Label") },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = longLabel,
onValueChange = { longLabel = it },
label = { Text("Long Label") },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = tinyLabel,
onValueChange = { tinyLabel = it },
label = { Text("Tiny Label") },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = inputLanguage,
onValueChange = { inputLanguage = it },
label = { Text("Input Language") },
modifier = Modifier.fillMaxWidth()
)
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
Checkbox(checked = enabled, onCheckedChange = { enabled = it })
Text("Enabled")
}
OutlinedTextField(
value = extras,
onValueChange = { extras = it },
label = { Text("EXTRAs (JSON)") },
modifier = Modifier.fillMaxWidth()
)
}
}
}
Loading
Loading