diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fbb3cdc --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Gradle +.gradle/ +build/ +**/build/ + +# Local configuration +local.properties + +# IntelliJ/Android Studio +.idea/ +*.iml + +# Logs +*.log + +# OS +.DS_Store +Thumbs.db + +# Keystores +*.jks +*.keystore + +# APKs +*.apk +*.ap_* + +# NDK +.obj/ +.externalNativeBuild/ +.cxx/ + +# Generated +captures/ +outputs/ + +# Gradle Wrapper JAR (optional, usually checked in) +gradle/wrapper/gradle-wrapper.jar \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..763a33a --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,120 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") +} + +android { + namespace = "com.example.voiceshopper" + compileSdk = 35 + + defaultConfig { + applicationId = "com.example.voiceshopper" + minSdk = 23 + targetSdk = 35 + versionCode = 1 + versionName = "1.0.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true + } + + buildTypes { + release { + isMinifyEnabled = true + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + debug { + isMinifyEnabled = false + } + } + + buildFeatures { + compose = true + buildConfig = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.14" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs += listOf( + "-Xcontext-receivers" + ) + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + + flavorDimensions += "features" + productFlavors { + create("baseline") { + dimension = "features" + buildConfigField("boolean", "FEATURE_GEOFENCE", "false") + } + create("geofence") { + dimension = "features" + buildConfigField("boolean", "FEATURE_GEOFENCE", "true") + } + } +} + +val composeBom = platform("androidx.compose:compose-bom:2024.09.02") + +dependencies { + implementation(composeBom) + androidTestImplementation(composeBom) + + // Compose + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.ui:ui-tooling-preview") + debugImplementation("androidx.compose.ui:ui-tooling") + implementation("androidx.activity:activity-compose:1.9.2") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4") + implementation("androidx.navigation:navigation-compose:2.8.0") + + // Room + KSP + implementation("androidx.room:room-runtime:2.6.1") + implementation("androidx.room:room-ktx:2.6.1") + ksp("androidx.room:room-compiler:2.6.1") + + // DataStore + implementation("androidx.datastore:datastore-preferences:1.1.1") + + // Coroutines + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") + + // Retrofit (stub) + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-moshi:2.11.0") + implementation("com.squareup.moshi:moshi-kotlin:1.15.1") + + // Play services for location (geofencing) + implementation("com.google.android.gms:play-services-location:21.3.0") + + // WorkManager (optional for reminders) + implementation("androidx.work:work-runtime-ktx:2.9.1") + + // Accompanist (optional permissions helper) + implementation("com.google.accompanist:accompanist-permissions:0.36.0") + + // Testing + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") + androidTestImplementation("androidx.compose.ui:ui-test-junit4") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..40078b5 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,5 @@ +-dontwarn kotlinx.coroutines.** +-keep class kotlinx.coroutines.** { *; } +-keepclassmembers class ** { + @android.webkit.JavascriptInterface ; +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b636f89 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/MainActivity.kt b/app/src/main/java/com/example/voiceshopper/MainActivity.kt new file mode 100644 index 0000000..c67e270 --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/MainActivity.kt @@ -0,0 +1,32 @@ +package com.example.voiceshopper + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color +import androidx.core.view.WindowCompat +import com.example.voiceshopper.ui.AppNavHost +import com.example.voiceshopper.ui.theme.VoiceShoppingTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + setContent { + VoiceShoppingTheme { + Surface(color = MaterialTheme.colorScheme.background) { + AppNav() + } + } + } + } +} + +@Composable +private fun AppNav() { + AppNavHost() +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/VoiceShoppingApp.kt b/app/src/main/java/com/example/voiceshopper/VoiceShoppingApp.kt new file mode 100644 index 0000000..903efbd --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/VoiceShoppingApp.kt @@ -0,0 +1,5 @@ +package com.example.voiceshopper + +import android.app.Application + +class VoiceShoppingApp : Application() \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/data/ShoppingRepository.kt b/app/src/main/java/com/example/voiceshopper/data/ShoppingRepository.kt new file mode 100644 index 0000000..32d2678 --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/data/ShoppingRepository.kt @@ -0,0 +1,82 @@ +package com.example.voiceshopper.data + +import android.content.Context +import com.example.voiceshopper.data.db.AppDatabase +import com.example.voiceshopper.data.db.ItemEntity +import com.example.voiceshopper.data.db.ShoppingListEntity +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map + +class ShoppingRepository private constructor(private val db: AppDatabase) { + + private val itemDao = db.itemDao() + private val listDao = db.shoppingListDao() + + suspend fun getOrCreateCurrentList(): ShoppingListEntity { + val latest = listDao.getLatestList() + if (latest != null) return latest + val createdId = listDao.insert( + ShoppingListEntity( + name = "Today", + createdAt = System.currentTimeMillis(), + total = 0.0 + ) + ) + return listDao.getLatestList()!! + } + + fun observeHistory(): Flow> = listDao.getHistory() + + suspend fun createNewList(name: String = "Today"): ShoppingListEntity { + val id = listDao.insert( + ShoppingListEntity( + name = name, + createdAt = System.currentTimeMillis(), + total = 0.0 + ) + ) + return listDao.getLatestList()!! + } + + fun observeItems(listId: Long): Flow> = itemDao.getItemsForList(listId) + + fun observePendingItems(listId: Long): Flow> = itemDao.getPendingItemsForList(listId) + + fun observePendingTotal(listId: Long): Flow = itemDao.getPendingTotalForList(listId) + + suspend fun addItem( + listId: Long, + name: String, + quantity: Double?, + unit: String?, + price: Double?, + owner: String? + ): Long { + val entity = ItemEntity( + name = name, + quantity = quantity, + unit = unit, + price = price, + owner = owner, + createdAt = System.currentTimeMillis(), + listId = listId, + bought = false + ) + return itemDao.insert(entity) + } + + suspend fun updateItem(item: ItemEntity) = itemDao.update(item) + + suspend fun deleteItem(id: Long) = itemDao.deleteById(id) + + suspend fun setBought(id: Long, bought: Boolean) = itemDao.setBought(id, bought) + + companion object { + @Volatile private var INSTANCE: ShoppingRepository? = null + fun get(context: Context): ShoppingRepository = INSTANCE ?: synchronized(this) { + val db = AppDatabase.get(context) + INSTANCE ?: ShoppingRepository(db).also { INSTANCE = it } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/data/db/AppDatabase.kt b/app/src/main/java/com/example/voiceshopper/data/db/AppDatabase.kt new file mode 100644 index 0000000..ec0bc2b --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/data/db/AppDatabase.kt @@ -0,0 +1,39 @@ +package com.example.voiceshopper.data.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +@Database( + entities = [ItemEntity::class, ShoppingListEntity::class], + version = 1, + exportSchema = true +) +abstract class AppDatabase : RoomDatabase() { + abstract fun itemDao(): ItemDao + abstract fun shoppingListDao(): ShoppingListDao + + companion object { + @Volatile private var INSTANCE: AppDatabase? = null + + fun get(context: Context): AppDatabase = INSTANCE ?: synchronized(this) { + INSTANCE ?: build(context).also { INSTANCE = it } + } + + private fun build(context: Context): AppDatabase { + return Room.databaseBuilder(context, AppDatabase::class.java, "voice_shopping.db") + .fallbackToDestructiveMigration() + .addCallback(object : Callback() { + override fun onCreate(db: SupportSQLiteDatabase) { + super.onCreate(db) + // Pre-populate a default shopping list named Today + db.execSQL("INSERT INTO shopping_lists(name, createdAt, total) VALUES('Today', strftime('%s','now')*1000, 0.0)") + } + }) + .build() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/data/db/Dao.kt b/app/src/main/java/com/example/voiceshopper/data/db/Dao.kt new file mode 100644 index 0000000..064d96f --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/data/db/Dao.kt @@ -0,0 +1,47 @@ +package com.example.voiceshopper.data.db + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import kotlinx.coroutines.flow.Flow + +@Dao +interface ShoppingListDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(list: ShoppingListEntity): Long + + @Update + suspend fun update(list: ShoppingListEntity) + + @Query("SELECT * FROM shopping_lists ORDER BY createdAt DESC LIMIT 1") + suspend fun getLatestList(): ShoppingListEntity? + + @Query("SELECT * FROM shopping_lists ORDER BY createdAt DESC") + fun getHistory(): Flow> +} + +@Dao +interface ItemDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(item: ItemEntity): Long + + @Update + suspend fun update(item: ItemEntity) + + @Query("DELETE FROM items WHERE id = :id") + suspend fun deleteById(id: Long) + + @Query("SELECT * FROM items WHERE listId = :listId ORDER BY createdAt DESC") + fun getItemsForList(listId: Long): Flow> + + @Query("SELECT * FROM items WHERE listId = :listId AND bought = 0 ORDER BY createdAt DESC") + fun getPendingItemsForList(listId: Long): Flow> + + @Query("UPDATE items SET bought = :bought WHERE id = :id") + suspend fun setBought(id: Long, bought: Boolean) + + @Query("SELECT IFNULL(SUM(price), 0.0) FROM items WHERE listId = :listId AND bought = 0") + fun getPendingTotalForList(listId: Long): Flow +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/data/db/Entities.kt b/app/src/main/java/com/example/voiceshopper/data/db/Entities.kt new file mode 100644 index 0000000..9e78609 --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/data/db/Entities.kt @@ -0,0 +1,41 @@ +package com.example.voiceshopper.data.db + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "shopping_lists", + indices = [Index("createdAt")] +) +data class ShoppingListEntity( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val name: String, + val createdAt: Long, + val total: Double = 0.0 +) + +@Entity( + tableName = "items", + foreignKeys = [ + ForeignKey( + entity = ShoppingListEntity::class, + parentColumns = ["id"], + childColumns = ["listId"], + onDelete = ForeignKey.CASCADE + ) + ], + indices = [Index("listId"), Index("name"), Index("owner")] +) +data class ItemEntity( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val name: String, + val quantity: Double?, + val unit: String?, + val price: Double?, + val owner: String?, + val createdAt: Long, + val bought: Boolean = false, + val listId: Long +) \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/domain/model/Models.kt b/app/src/main/java/com/example/voiceshopper/domain/model/Models.kt new file mode 100644 index 0000000..c2169d5 --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/domain/model/Models.kt @@ -0,0 +1,26 @@ +package com.example.voiceshopper.domain.model + +import com.example.voiceshopper.data.db.ItemEntity +import com.example.voiceshopper.data.db.ShoppingListEntity + +data class ShoppingList( + val id: Long, + val name: String, + val createdAt: Long, + val total: Double +) + +data class Item( + val id: Long, + val name: String, + val quantity: Double?, + val unit: String?, + val price: Double?, + val owner: String?, + val createdAt: Long, + val bought: Boolean, + val listId: Long +) + +fun ShoppingListEntity.toDomain() = ShoppingList(id, name, createdAt, total) +fun ItemEntity.toDomain() = Item(id, name, quantity, unit, price, owner, createdAt, bought, listId) \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/ui/AppNavHost.kt b/app/src/main/java/com/example/voiceshopper/ui/AppNavHost.kt new file mode 100644 index 0000000..2eac7df --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/ui/AppNavHost.kt @@ -0,0 +1,65 @@ +package com.example.voiceshopper.ui + +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.painterResource +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.example.voiceshopper.R +import com.example.voiceshopper.ui.screens.HistoryScreen +import com.example.voiceshopper.ui.screens.HomeScreen +import com.example.voiceshopper.ui.screens.SettingsScreen +import com.example.voiceshopper.ui.screens.VoiceAddScreen + +sealed class Dest(val route: String, val title: String, val icon: Int) { + data object Home : Dest("home", "Home", R.drawable.ic_home) + data object Voice : Dest("voice", "Voice", R.drawable.ic_mic) + data object History : Dest("history", "History", R.drawable.ic_history) + data object Settings : Dest("settings", "Settings", R.drawable.ic_settings) +} + +@Composable +fun AppNavHost() { + val navController = rememberNavController() + val items = listOf(Dest.Home, Dest.Voice, Dest.History, Dest.Settings) + + Scaffold( + bottomBar = { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + NavigationBar { + items.forEach { item -> + NavigationBarItem( + icon = { Icon(painterResource(item.icon), contentDescription = item.title) }, + label = { Text(item.title) }, + selected = currentRoute == item.route, + onClick = { + navController.navigate(item.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } + } + ) { paddingValues -> + NavHost(navController, startDestination = Dest.Home.route) { + composable(Dest.Home.route) { HomeScreen(paddingValues) } + composable(Dest.Voice.route) { VoiceAddScreen(paddingValues) } + composable(Dest.History.route) { HistoryScreen(paddingValues) } + composable(Dest.Settings.route) { SettingsScreen(paddingValues) } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/ui/screens/HistoryScreen.kt b/app/src/main/java/com/example/voiceshopper/ui/screens/HistoryScreen.kt new file mode 100644 index 0000000..44288ec --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/ui/screens/HistoryScreen.kt @@ -0,0 +1,22 @@ +package com.example.voiceshopper.ui.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun HistoryScreen(padding: PaddingValues) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(24.dp) + ) { + Text("History") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/ui/screens/HomeScreen.kt b/app/src/main/java/com/example/voiceshopper/ui/screens/HomeScreen.kt new file mode 100644 index 0000000..8372f62 --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/ui/screens/HomeScreen.kt @@ -0,0 +1,48 @@ +package com.example.voiceshopper.ui.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Help +import androidx.compose.material.icons.filled.Mic +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun HomeScreen(padding: PaddingValues) { + Scaffold( + topBar = { TopAppBar(title = { Text("Today") }) }, + floatingActionButton = { + Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { + FloatingActionButton(onClick = { /* TODO: navigate to voice */ }) { + Icon(Icons.Default.Mic, contentDescription = "Speak") + } + } + } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(innerPadding), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Your shopping list will appear here") + IconButton(onClick = { /* TODO: Ask via TTS */ }) { + Icon(Icons.Default.Help, contentDescription = "Ask") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/ui/screens/SettingsScreen.kt b/app/src/main/java/com/example/voiceshopper/ui/screens/SettingsScreen.kt new file mode 100644 index 0000000..bdc63af --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/ui/screens/SettingsScreen.kt @@ -0,0 +1,22 @@ +package com.example.voiceshopper.ui.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun SettingsScreen(padding: PaddingValues) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(24.dp) + ) { + Text("Settings") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/ui/screens/VoiceAddScreen.kt b/app/src/main/java/com/example/voiceshopper/ui/screens/VoiceAddScreen.kt new file mode 100644 index 0000000..1fd6619 --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/ui/screens/VoiceAddScreen.kt @@ -0,0 +1,24 @@ +package com.example.voiceshopper.ui.screens + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun VoiceAddScreen(padding: PaddingValues) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + .padding(24.dp) + ) { + Text("Voice Add") + Button(onClick = { /* TODO start listening */ }) { Text("Start") } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/ui/theme/Theme.kt b/app/src/main/java/com/example/voiceshopper/ui/theme/Theme.kt new file mode 100644 index 0000000..2f71d7c --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/ui/theme/Theme.kt @@ -0,0 +1,20 @@ +package com.example.voiceshopper.ui.theme + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable + +private val LightColors = lightColorScheme() +private val DarkColors = darkColorScheme() + +@Composable +fun VoiceShoppingTheme( + darkTheme: Boolean = false, + content: @Composable () -> Unit +) { + MaterialTheme( + colorScheme = if (darkTheme) DarkColors else LightColors, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/voiceshopper/util/ServiceLocator.kt b/app/src/main/java/com/example/voiceshopper/util/ServiceLocator.kt new file mode 100644 index 0000000..60ccc99 --- /dev/null +++ b/app/src/main/java/com/example/voiceshopper/util/ServiceLocator.kt @@ -0,0 +1,8 @@ +package com.example.voiceshopper.util + +import android.content.Context +import com.example.voiceshopper.data.ShoppingRepository + +object ServiceLocator { + fun shoppingRepository(context: Context): ShoppingRepository = ShoppingRepository.get(context) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 0000000..92097e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..998a5c5 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_mic.xml b/app/src/main/res/drawable/ic_mic.xml new file mode 100644 index 0000000..1b1b8cf --- /dev/null +++ b/app/src/main/res/drawable/ic_mic.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..b996e91 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..b92d83c --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml new file mode 100644 index 0000000..b00c406 --- /dev/null +++ b/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,37 @@ + + + वॉइस शॉपिंग असिस्टेंट + बोलकर जोड़ें + पूछें + मेरी सूची में क्या है? + कितना लेना था? + वॉइस से जोड़ें + इतिहास + सेटिंग्स + पुष्टि + रद्द करें + संपादित करें + हटाएँ + खरीद लिया + मालिक + श्रेणी + मात्रा + कीमत + मैन्युअल जोड़ें + कुल + बजट + दैनिक बजट + साप्ताहिक बजट + आवाज़ की गति + आवाज़ की पिच + भाषा + अंग्रेज़ी + हिंदी + डेटा निर्यात + डेटा आयात + बजट से अधिक + यह आइटम जोड़ने से बजट से अधिक हो जाएगा + वॉइस इनपुट के लिए माइक्रोफोन की अनुमति चाहिए। + जियोफेंस रिमाइंडर के लिए स्थान की अनुमति चाहिए। + ठीक है + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..6e7aadc --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,37 @@ + + + Voice Shopping Assistant + Speak to Add + Ask + What's on my list? + Kitna lena tha? + Voice Add + History + Settings + Confirm + Cancel + Edit + Delete + Bought + Owner + Category + Quantity + Price + Add Manually + Total + Budget + Daily budget + Weekly budget + TTS Speed + TTS Pitch + Language + English + Hindi + Export data + Import data + Budget exceeded + Adding this item exceeds your budget + Microphone access is needed for voice input. + Location is needed for geofence reminders. + OK + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..89df0c8 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..dc59179 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,11 @@ +buildscript { + dependencies { + // If needed, add classpaths here + } +} + +plugins { + id("com.android.application") version "8.5.2" apply false + id("org.jetbrains.kotlin.android") version "2.0.10" apply false + id("com.google.devtools.ksp") version "2.0.10-1.0.24" apply false +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..53d3db1 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 +android.useAndroidX=true +kotlin.code.style=official +kotlin.jvm.target=17 +android.nonTransitiveRClass=true +android.enableR8.fullMode=true \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..39e9b34 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "VoiceShoppingAssistant" +include(":app") \ No newline at end of file