From 10abb1913c5b32bd5484e9821a740f690e62c5fb Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Thu, 22 Jan 2026 16:48:45 -0800 Subject: [PATCH 1/2] Introduce a Welcome Page --- .../executorchllamademo/UIWorkflowTest.kt | 114 ++++++-- .../app/src/main/AndroidManifest.xml | 20 +- .../AppSettingsActivity.kt | 69 +++++ .../executorchllamademo/LogsActivity.kt | 4 +- .../executorchllamademo/MainActivity.kt | 22 +- .../executorchllamademo/SettingsActivity.kt | 5 +- .../executorchllamademo/WelcomeActivity.kt | 75 ++++++ .../ui/screens/AppSettingsScreen.kt | 250 ++++++++++++++++++ .../ui/screens/ChatScreen.kt | 50 +++- .../ui/screens/LogsScreen.kt | 14 +- .../ui/screens/SettingsScreen.kt | 33 +-- .../ui/screens/WelcomeScreen.kt | 215 +++++++++++++++ 12 files changed, 792 insertions(+), 79 deletions(-) create mode 100644 llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/AppSettingsActivity.kt create mode 100644 llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt create mode 100644 llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/AppSettingsScreen.kt create mode 100644 llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/WelcomeScreen.kt diff --git a/llm/android/LlamaDemo/app/src/androidTest/java/com/example/executorchllamademo/UIWorkflowTest.kt b/llm/android/LlamaDemo/app/src/androidTest/java/com/example/executorchllamademo/UIWorkflowTest.kt index 0a4f08cde3..8659b52d35 100644 --- a/llm/android/LlamaDemo/app/src/androidTest/java/com/example/executorchllamademo/UIWorkflowTest.kt +++ b/llm/android/LlamaDemo/app/src/androidTest/java/com/example/executorchllamademo/UIWorkflowTest.kt @@ -44,6 +44,7 @@ import org.junit.runner.RunWith * - Push a tokenizer file (.bin, .json, or .model) to /data/local/tmp/llama/ * * This test validates: + * - Welcome screen navigation * - Settings screen shows empty model/tokenizer paths by default * - File selection dialogs display pushed files * - User can select model and tokenizer files @@ -65,7 +66,7 @@ class UIWorkflowTest { } @get:Rule - val composeTestRule = createAndroidComposeRule() + val composeTestRule = createAndroidComposeRule() private lateinit var modelFile: String private lateinit var tokenizerFile: String @@ -88,36 +89,42 @@ class UIWorkflowTest { } /** - * Clears chat history via the Settings UI. + * Clears chat history via the App Settings UI. * This ensures each test starts with a clean state. + * Must be called from the Welcome screen. */ private fun clearChatHistory() { composeTestRule.waitForIdle() - // Go to settings + // Go to App Settings from Welcome screen try { - composeTestRule.onNodeWithContentDescription("Settings").performClick() + composeTestRule.onNodeWithText("App Settings").performClick() composeTestRule.waitUntil(timeoutMillis = 3000) { - composeTestRule.onAllNodesWithText("Clear Chat History") + composeTestRule.onAllNodesWithText("Clear Conversation History") .fetchSemanticsNodes().isNotEmpty() } } catch (e: Exception) { - Log.d(TAG, "Could not open settings to clear history: ${e.message}") + Log.d(TAG, "Could not open App Settings to clear history: ${e.message}") return } - // Click Clear Chat History button (clears immediately, no confirmation dialog) + // Click Clear Conversation History button try { - composeTestRule.onNodeWithText("Clear Chat History").performClick() + composeTestRule.onNodeWithText("Clear Conversation History").performClick() + composeTestRule.waitUntil(timeoutMillis = 3000) { + composeTestRule.onAllNodesWithText("Clear").fetchSemanticsNodes().isNotEmpty() + } + // Confirm in dialog + composeTestRule.onNodeWithText("Clear").performClick() composeTestRule.waitForIdle() Log.i(TAG, "Chat history cleared") } catch (e: Exception) { Log.d(TAG, "Could not clear chat history: ${e.message}") } - // Go back to chat screen using system back + // Go back to Welcome screen using back button try { - Espresso.pressBack() + composeTestRule.onNodeWithContentDescription("Back").performClick() composeTestRule.waitForIdle() } catch (e: Exception) { Log.d(TAG, "Could not press back after clearing history: ${e.message}") @@ -125,14 +132,14 @@ class UIWorkflowTest { } /** - * Navigates to settings and selects model/tokenizer files. + * Navigates from Welcome screen to settings and selects model/tokenizer files. * Returns true if successful. */ private fun loadModel(): Boolean { - // Click settings button - composeTestRule.onNodeWithContentDescription("Settings").performClick() + // Click "Load local model" card on Welcome screen + composeTestRule.onNodeWithText("Load local model").performClick() composeTestRule.waitUntil(timeoutMillis = 5001) { - composeTestRule.onAllNodesWithText("Settings").fetchSemanticsNodes().isNotEmpty() + composeTestRule.onAllNodesWithText("Select a Model").fetchSemanticsNodes().isNotEmpty() } // Click model row to open model selection dialog @@ -266,7 +273,7 @@ class UIWorkflowTest { /** * Tests the complete model loading workflow: - * 1. Click settings button + * 1. Click "Load Local LLM Model" card on Welcome screen * 2. Verify model path and tokenizer path show default "no selection" text * 3. Click model selection, select model.pte * 4. Click tokenizer selection, select tokenizer.model @@ -276,14 +283,14 @@ class UIWorkflowTest { fun testModelLoadingWorkflow() { composeTestRule.waitForIdle() - // Click settings button - composeTestRule.onNodeWithContentDescription("Settings").performClick() + // Click "Load local model" card on Welcome screen + composeTestRule.onNodeWithText("Load local model").performClick() composeTestRule.waitUntil(timeoutMillis = 5005) { - composeTestRule.onAllNodesWithText("Settings").fetchSemanticsNodes().isNotEmpty() + composeTestRule.onAllNodesWithText("Select a Model").fetchSemanticsNodes().isNotEmpty() } // Verify we're in settings - composeTestRule.onNodeWithText("Settings").assertIsDisplayed() + composeTestRule.onNodeWithText("Select a Model").assertIsDisplayed() composeTestRule.onNodeWithText("Load Model").assertIsDisplayed() composeTestRule.onNodeWithText("no model selected").assertIsDisplayed() composeTestRule.onNodeWithText("no tokenizer selected").assertIsDisplayed() @@ -488,14 +495,14 @@ class UIWorkflowTest { fun testNoFilesInDirectory() { composeTestRule.waitForIdle() - // Go to settings - composeTestRule.onNodeWithContentDescription("Settings").performClick() + // Go to settings from Welcome screen + composeTestRule.onNodeWithText("Load local model").performClick() composeTestRule.waitUntil(timeoutMillis = 5011) { - composeTestRule.onAllNodesWithText("Settings").fetchSemanticsNodes().isNotEmpty() + composeTestRule.onAllNodesWithText("Select a Model").fetchSemanticsNodes().isNotEmpty() } // Verify settings screen - composeTestRule.onNodeWithText("Settings").assertIsDisplayed() + composeTestRule.onNodeWithText("Select a Model").assertIsDisplayed() // Click model selection composeTestRule.onNodeWithText("Model").performClick() @@ -523,10 +530,10 @@ class UIWorkflowTest { fun testCancelFileSelection() { composeTestRule.waitForIdle() - // Go to settings - composeTestRule.onNodeWithContentDescription("Settings").performClick() + // Go to settings from Welcome screen + composeTestRule.onNodeWithText("Load local model").performClick() composeTestRule.waitUntil(timeoutMillis = 5013) { - composeTestRule.onAllNodesWithText("Settings").fetchSemanticsNodes().isNotEmpty() + composeTestRule.onAllNodesWithText("Select a Model").fetchSemanticsNodes().isNotEmpty() } // Verify initial state @@ -574,10 +581,10 @@ class UIWorkflowTest { fun testLoadButtonDisabledState() { composeTestRule.waitForIdle() - // Go to settings - composeTestRule.onNodeWithContentDescription("Settings").performClick() + // Go to settings from Welcome screen + composeTestRule.onNodeWithText("Load local model").performClick() composeTestRule.waitUntil(timeoutMillis = 5018) { - composeTestRule.onAllNodesWithText("Settings").fetchSemanticsNodes().isNotEmpty() + composeTestRule.onAllNodesWithText("Select a Model").fetchSemanticsNodes().isNotEmpty() } // Verify load button is initially disabled @@ -789,4 +796,53 @@ class UIWorkflowTest { Log.i(TAG, "Media buttons not present - might be MediaTek backend") } } + + /** + * Tests Welcome screen displays and navigation works correctly. + */ + @Test + fun testWelcomeScreenNavigation() { + composeTestRule.waitForIdle() + + // Verify Welcome screen elements are displayed + composeTestRule.onNodeWithText("ExecuTorch Llama Demo").assertIsDisplayed() + composeTestRule.onNodeWithText("Welcome to ExecuTorch Llama Demo").assertIsDisplayed() + composeTestRule.onNodeWithText("Load local model").assertIsDisplayed() + composeTestRule.onNodeWithText("App Settings").assertIsDisplayed() + + // Test navigation to App Settings + composeTestRule.onNodeWithText("App Settings").performClick() + composeTestRule.waitUntil(timeoutMillis = 3000) { + composeTestRule.onAllNodesWithText("App Settings", useUnmergedTree = true) + .fetchSemanticsNodes().size >= 1 + } + + // Verify App Settings screen + composeTestRule.onNodeWithText("Appearance").assertIsDisplayed() + composeTestRule.onNodeWithText("Theme").assertIsDisplayed() + composeTestRule.onNodeWithText("Clear Conversation History").assertIsDisplayed() + + // Go back to Welcome screen + composeTestRule.onNodeWithContentDescription("Back").performClick() + composeTestRule.waitUntil(timeoutMillis = 3000) { + composeTestRule.onAllNodesWithText("ExecuTorch Llama Demo").fetchSemanticsNodes().isNotEmpty() + } + + // Verify we're back on Welcome screen + composeTestRule.onNodeWithText("ExecuTorch Llama Demo").assertIsDisplayed() + composeTestRule.onNodeWithText("Load local model").assertIsDisplayed() + + // Test navigation to Model Settings + composeTestRule.onNodeWithText("Load local model").performClick() + composeTestRule.waitUntil(timeoutMillis = 3000) { + composeTestRule.onAllNodesWithText("Select a Model").fetchSemanticsNodes().isNotEmpty() + } + + // Verify Model Settings screen + composeTestRule.onNodeWithText("Select a Model").assertIsDisplayed() + composeTestRule.onNodeWithText("Backend").assertIsDisplayed() + composeTestRule.onNodeWithText("Load Model").assertIsDisplayed() + + Log.i(TAG, "Welcome screen navigation test completed successfully") + } } diff --git a/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml b/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml index 22efc6f6b5..986414d97e 100644 --- a/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml +++ b/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml @@ -24,6 +24,19 @@ + + + + + + + + - - - - - diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/AppSettingsActivity.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/AppSettingsActivity.kt new file mode 100644 index 0000000000..9c00fa0928 --- /dev/null +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/AppSettingsActivity.kt @@ -0,0 +1,69 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.example.executorchllamademo + +import android.os.Build +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.core.content.ContextCompat +import com.example.executorchllamademo.ui.screens.AppSettingsScreen +import com.example.executorchllamademo.ui.theme.LlamaDemoTheme + +class AppSettingsActivity : ComponentActivity() { + + private var appearanceMode by mutableStateOf(AppearanceMode.SYSTEM) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (Build.VERSION.SDK_INT >= 21) { + window.statusBarColor = ContextCompat.getColor(this, R.color.status_bar) + window.navigationBarColor = ContextCompat.getColor(this, R.color.nav_bar) + } + + loadAppearanceMode() + + setContent { + val isDarkTheme = when (appearanceMode) { + AppearanceMode.LIGHT -> false + AppearanceMode.DARK -> true + AppearanceMode.SYSTEM -> isSystemInDarkTheme() + } + + LlamaDemoTheme(darkTheme = isDarkTheme) { + Surface(modifier = Modifier.fillMaxSize()) { + AppSettingsScreen( + onBackPressed = { finish() }, + onAppearanceChanged = { mode -> + appearanceMode = mode + } + ) + } + } + } + } + + private fun loadAppearanceMode() { + val prefs = DemoSharedPreferences(this) + appearanceMode = prefs.getAppSettings().appearanceMode + } + + override fun onResume() { + super.onResume() + loadAppearanceMode() + } +} diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/LogsActivity.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/LogsActivity.kt index de76756c8e..5078c39643 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/LogsActivity.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/LogsActivity.kt @@ -46,7 +46,9 @@ class LogsActivity : ComponentActivity() { LlamaDemoTheme(darkTheme = isDarkTheme) { Surface(modifier = Modifier.fillMaxSize()) { - LogsScreen() + LogsScreen( + onBackClick = { finish() } + ) } } } diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/MainActivity.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/MainActivity.kt index 7aa68d3c92..d2a688a31b 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/MainActivity.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/MainActivity.kt @@ -9,7 +9,6 @@ package com.example.executorchllamademo import android.Manifest -import android.app.AlertDialog import android.content.ContentValues import android.content.Intent import android.content.pm.PackageManager @@ -81,6 +80,10 @@ class MainActivity : ComponentActivity() { ChatScreen( viewModel = viewModel, + onBackClick = { + startActivity(Intent(this@MainActivity, WelcomeActivity::class.java)) + finish() + }, onSettingsClick = { startActivity(Intent(this@MainActivity, SettingsActivity::class.java)) }, @@ -103,8 +106,9 @@ class MainActivity : ComponentActivity() { launchCamera() } }, - onAudioClick = { _ -> - showAudioFileSelector() + audioFiles = SettingsActivity.listLocalFile("/data/local/tmp/audio/", arrayOf(".bin")).toList(), + onAudioFileSelected = { audioFile -> + chatViewModel?.setAudioFile(audioFile) } ) } @@ -164,18 +168,6 @@ class MainActivity : ComponentActivity() { cameraImageUri?.let { cameraRoll.launch(it) } } - private fun showAudioFileSelector() { - val audioFiles = SettingsActivity.listLocalFile("/data/local/tmp/audio/", arrayOf(".bin")) - AlertDialog.Builder(this) - .setTitle("Select audio feature path") - .setSingleChoiceItems(audioFiles, -1) { dialog, item -> - chatViewModel?.setAudioFile(audioFiles[item]) - dialog.dismiss() - } - .create() - .show() - } - private fun loadAppearanceMode() { val prefs = DemoSharedPreferences(this) appearanceMode = prefs.getAppSettings().appearanceMode diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/SettingsActivity.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/SettingsActivity.kt index 0d3f682745..80b7fd81d8 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/SettingsActivity.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/SettingsActivity.kt @@ -8,6 +8,7 @@ package com.example.executorchllamademo +import android.content.Intent import android.os.Build import android.os.Bundle import androidx.activity.ComponentActivity @@ -57,7 +58,9 @@ class SettingsActivity : ComponentActivity() { finish() }, onLoadModel = { - // Settings are saved by viewModel.confirmLoadModel() + // Navigate to MainActivity (conversation) after loading model + startActivity(Intent(this@SettingsActivity, MainActivity::class.java)) + finish() }, onAppearanceChanged = { mode -> appearanceMode = mode diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt new file mode 100644 index 0000000000..4a67040e13 --- /dev/null +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.example.executorchllamademo + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.core.content.ContextCompat +import com.example.executorchllamademo.ui.screens.WelcomeScreen +import com.example.executorchllamademo.ui.theme.LlamaDemoTheme + +class WelcomeActivity : ComponentActivity() { + + private var appearanceMode by mutableStateOf(AppearanceMode.SYSTEM) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (Build.VERSION.SDK_INT >= 21) { + window.statusBarColor = ContextCompat.getColor(this, R.color.status_bar) + window.navigationBarColor = ContextCompat.getColor(this, R.color.nav_bar) + } + + loadAppearanceMode() + + setContent { + val isDarkTheme = when (appearanceMode) { + AppearanceMode.LIGHT -> false + AppearanceMode.DARK -> true + AppearanceMode.SYSTEM -> isSystemInDarkTheme() + } + + LlamaDemoTheme(darkTheme = isDarkTheme) { + Surface(modifier = Modifier.fillMaxSize()) { + WelcomeScreen( + onLoadModelClick = { + startActivity(Intent(this@WelcomeActivity, SettingsActivity::class.java)) + }, + onAppSettingsClick = { + startActivity(Intent(this@WelcomeActivity, AppSettingsActivity::class.java)) + }, + onStartChatClick = { + startActivity(Intent(this@WelcomeActivity, MainActivity::class.java)) + } + ) + } + } + } + } + + private fun loadAppearanceMode() { + val prefs = DemoSharedPreferences(this) + appearanceMode = prefs.getAppSettings().appearanceMode + } + + override fun onResume() { + super.onResume() + loadAppearanceMode() + } +} diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/AppSettingsScreen.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/AppSettingsScreen.kt new file mode 100644 index 0000000000..29d53901b0 --- /dev/null +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/AppSettingsScreen.kt @@ -0,0 +1,250 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.example.executorchllamademo.ui.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Warning +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.executorchllamademo.AppSettings +import com.example.executorchllamademo.AppearanceMode +import com.example.executorchllamademo.DemoSharedPreferences +import com.example.executorchllamademo.ModuleSettings +import com.example.executorchllamademo.ui.components.SettingsRow +import com.example.executorchllamademo.ui.theme.BtnEnabled +import com.example.executorchllamademo.ui.theme.LocalAppColors + +@Composable +fun AppSettingsScreen( + onBackPressed: () -> Unit = {}, + onAppearanceChanged: (AppearanceMode) -> Unit = {} +) { + val context = LocalContext.current + val scrollState = rememberScrollState() + val appColors = LocalAppColors.current + + var appSettings by remember { mutableStateOf(AppSettings()) } + var moduleSettings by remember { mutableStateOf(ModuleSettings()) } + var showAppearanceDialog by remember { mutableStateOf(false) } + var showClearChatDialog by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + val prefs = DemoSharedPreferences(context) + appSettings = prefs.getAppSettings() + moduleSettings = prefs.getModuleSettings() + } + + Column( + modifier = Modifier + .fillMaxSize() + .background(appColors.settingsBackground) + ) { + // Top banner + Row( + modifier = Modifier + .fillMaxWidth() + .background(appColors.navBar) + .padding(start = 4.dp, end = 16.dp, top = 4.dp, bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = onBackPressed, + modifier = Modifier.size(48.dp) + ) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back", + tint = appColors.textOnNavBar + ) + } + Text( + text = "App Settings", + color = appColors.textOnNavBar, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) + ) + } + + // Scrollable content + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(12.dp) + ) { + // Appearance section header + Text( + text = "Appearance", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = appColors.settingsText + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // Appearance selector + SettingsRow( + label = "Theme", + value = appSettings.appearanceMode.displayName, + onClick = { showAppearanceDialog = true } + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // Conversation section header + Text( + text = "Conversation", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = appColors.settingsText + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // Clear Chat button + Button( + onClick = { showClearChatDialog = true }, + modifier = Modifier.fillMaxWidth(), + colors = ButtonDefaults.buttonColors(containerColor = BtnEnabled), + shape = RoundedCornerShape(8.dp) + ) { + Text("Clear Conversation History", color = Color.White) + } + + Spacer(modifier = Modifier.height(32.dp)) + } + } + + // Appearance Dialog + if (showAppearanceDialog) { + AppearanceSelectionDialog( + currentMode = appSettings.appearanceMode, + onSelect = { mode -> + appSettings = appSettings.copy(appearanceMode = mode) + val prefs = DemoSharedPreferences(context) + prefs.saveAppSettings(appSettings) + onAppearanceChanged(mode) + showAppearanceDialog = false + }, + onDismiss = { showAppearanceDialog = false } + ) + } + + // Clear Chat Confirmation Dialog + if (showClearChatDialog) { + AlertDialog( + onDismissRequest = { showClearChatDialog = false }, + icon = { + Icon( + imageVector = Icons.Filled.Warning, + contentDescription = null + ) + }, + title = { Text("Clear Conversation") }, + text = { Text("Are you sure you want to clear all conversation history?") }, + confirmButton = { + TextButton( + onClick = { + val prefs = DemoSharedPreferences(context) + moduleSettings = moduleSettings.copy(isClearChatHistory = true) + prefs.saveModuleSettings(moduleSettings) + showClearChatDialog = false + } + ) { + Text("Clear") + } + }, + dismissButton = { + TextButton(onClick = { showClearChatDialog = false }) { + Text("Cancel") + } + } + ) + } +} + +@Composable +private fun AppearanceSelectionDialog( + currentMode: AppearanceMode, + onSelect: (AppearanceMode) -> Unit, + onDismiss: () -> Unit +) { + var selectedOption by remember { mutableStateOf(null) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { Text("Select Theme") }, + text = { + Column { + AppearanceMode.values().forEach { mode -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + selectedOption = mode + onSelect(mode) + } + .padding(vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedOption == mode || (selectedOption == null && currentMode == mode), + onClick = null + ) + Text( + text = mode.displayName, + modifier = Modifier.padding(start = 8.dp), + fontSize = 14.sp + ) + } + } + } + }, + confirmButton = {}, + dismissButton = { + TextButton(onClick = onDismiss) { + Text("Cancel") + } + } + ) +} diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/ChatScreen.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/ChatScreen.kt index ae8851ab0b..abb7ada212 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/ChatScreen.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/ChatScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Article import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.AlertDialog @@ -34,7 +35,10 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLifecycleOwner @@ -54,12 +58,15 @@ import androidx.lifecycle.LifecycleEventObserver @Composable fun ChatScreen( viewModel: ChatViewModel, + onBackClick: () -> Unit, onSettingsClick: () -> Unit, onLogsClick: () -> Unit, onGalleryClick: () -> Unit, onCameraClick: () -> Unit, - onAudioClick: (List) -> Unit + audioFiles: List, + onAudioFileSelected: (String) -> Unit ) { + var showAudioDialog by remember { mutableStateOf(false) } val listState = rememberLazyListState() val appColors = LocalAppColors.current val focusManager = LocalFocusManager.current @@ -110,6 +117,15 @@ fun ChatScreen( fontWeight = FontWeight.Bold ) }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back", + tint = appColors.textOnNavBar + ) + } + }, actions = { Text( text = viewModel.ramUsage, @@ -183,7 +199,7 @@ fun ChatScreen( onAddMediaClick = { viewModel.toggleMediaSelector() }, onGalleryClick = onGalleryClick, onCameraClick = onCameraClick, - onAudioClick = { onAudioClick(emptyList()) }, + onAudioClick = { showAudioDialog = true }, selectedImages = viewModel.selectedImages, onRemoveImage = { viewModel.removeImage(it) }, onAddMoreImages = onGalleryClick, @@ -206,4 +222,34 @@ fun ChatScreen( } ) } + + // Audio file selection dialog + if (showAudioDialog) { + AlertDialog( + onDismissRequest = { showAudioDialog = false }, + title = { Text("Select audio feature path") }, + text = { + Column { + audioFiles.forEach { audioFile -> + Text( + text = audioFile, + modifier = Modifier + .fillMaxWidth() + .clickable { + onAudioFileSelected(audioFile) + showAudioDialog = false + } + .padding(vertical = 12.dp) + ) + } + } + }, + confirmButton = {}, + dismissButton = { + TextButton(onClick = { showAudioDialog = false }) { + Text("Cancel") + } + } + ) + } } diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/LogsScreen.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/LogsScreen.kt index a9c02c05a4..442705614b 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/LogsScreen.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/LogsScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.outlined.ContentCopy @@ -51,7 +52,8 @@ import com.example.executorchllamademo.ui.viewmodel.LogsViewModel @Composable fun LogsScreen( - viewModel: LogsViewModel = viewModel() + viewModel: LogsViewModel = viewModel(), + onBackClick: () -> Unit = {} ) { var showClearDialog by remember { mutableStateOf(false) } val lifecycleOwner = LocalLifecycleOwner.current @@ -83,9 +85,17 @@ fun LogsScreen( modifier = Modifier .fillMaxWidth() .background(appColors.navBar) - .padding(horizontal = 16.dp, vertical = 12.dp), + .padding(horizontal = 4.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back", + tint = appColors.textOnNavBar + ) + } + Text( text = "Logs", color = appColors.textOnNavBar, diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/SettingsScreen.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/SettingsScreen.kt index bd4e816921..62f61b7b42 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/SettingsScreen.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/SettingsScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowRight import androidx.compose.material.icons.filled.Refresh @@ -95,11 +96,18 @@ fun SettingsScreen( modifier = Modifier .fillMaxWidth() .background(appColors.navBar) - .padding(horizontal = 16.dp, vertical = 12.dp), + .padding(horizontal = 4.dp, vertical = 4.dp), verticalAlignment = Alignment.CenterVertically ) { + IconButton(onClick = onBackPressed) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back", + tint = appColors.textOnNavBar + ) + } Text( - text = "Settings", + text = "Select a Model", color = appColors.textOnNavBar, fontSize = 18.sp, fontWeight = FontWeight.Bold, @@ -114,15 +122,6 @@ fun SettingsScreen( .verticalScroll(scrollState) .padding(12.dp) ) { - // Appearance selector (first) - SettingsRow( - label = "Appearance", - value = viewModel.appSettings.appearanceMode.displayName, - onClick = { viewModel.showAppearanceDialog = true } - ) - - Spacer(modifier = Modifier.height(8.dp)) - // Backend selector SettingsRow( label = "Backend", @@ -323,18 +322,6 @@ fun SettingsScreen( } } - Spacer(modifier = Modifier.height(24.dp)) - - // Clear Chat button - Button( - onClick = { viewModel.confirmClearChat() }, - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = BtnEnabled), - shape = RoundedCornerShape(8.dp) - ) { - Text("Clear Chat History", color = Color.White) - } - Spacer(modifier = Modifier.height(32.dp)) } } diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/WelcomeScreen.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/WelcomeScreen.kt new file mode 100644 index 0000000000..d85ebf8c28 --- /dev/null +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/WelcomeScreen.kt @@ -0,0 +1,215 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.example.executorchllamademo.ui.screens + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.executorchllamademo.ui.theme.LocalAppColors + +@Composable +fun WelcomeScreen( + onLoadModelClick: () -> Unit = {}, + onAppSettingsClick: () -> Unit = {}, + onStartChatClick: () -> Unit = {} +) { + val appColors = LocalAppColors.current + val scrollState = rememberScrollState() + + Column( + modifier = Modifier + .fillMaxSize() + .background(appColors.settingsBackground) + ) { + // Top banner + Row( + modifier = Modifier + .fillMaxWidth() + .background(appColors.navBar) + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "ExecuTorch Llama Demo", + color = appColors.textOnNavBar, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f) + ) + } + + // Scrollable content + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "Welcome to ExecuTorch Llama Demo", + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = appColors.settingsText + ) + + Text( + text = "Configure your LLM model and app settings to get started.", + fontSize = 14.sp, + color = appColors.settingsSecondaryText + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // Load Local LLM Model Card + WelcomeCard( + title = "Load local model", + description = "Select your own model and tokenizer path.", + icon = Icons.Filled.PlayArrow, + onClick = onLoadModelClick + ) + + // App Settings Card + WelcomeCardNoDescription( + title = "App Settings", + icon = Icons.Filled.Settings, + onClick = onAppSettingsClick + ) + + Spacer(modifier = Modifier.height(16.dp)) + } + } +} + +@Composable +private fun WelcomeCard( + title: String, + description: String, + icon: ImageVector, + onClick: () -> Unit +) { + val appColors = LocalAppColors.current + + Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = appColors.settingsRowBackground + ), + elevation = CardDefaults.cardElevation( + defaultElevation = 2.dp + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = title, + tint = appColors.settingsText, + modifier = Modifier.size(32.dp) + ) + + Column( + modifier = Modifier + .weight(1f) + .padding(start = 16.dp) + ) { + Text( + text = title, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = appColors.settingsText + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = description, + fontSize = 13.sp, + color = appColors.settingsSecondaryText + ) + } + } + } +} + +@Composable +private fun WelcomeCardNoDescription( + title: String, + icon: ImageVector, + onClick: () -> Unit +) { + val appColors = LocalAppColors.current + + Card( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = appColors.settingsRowBackground + ), + elevation = CardDefaults.cardElevation( + defaultElevation = 2.dp + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = title, + tint = appColors.settingsText, + modifier = Modifier.size(32.dp) + ) + + Text( + text = title, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = appColors.settingsText, + modifier = Modifier.padding(start = 16.dp) + ) + } + } +} From eb035183e4348dcee94a1763917e6b578ab9c4f0 Mon Sep 17 00:00:00 2001 From: Hansong Zhang Date: Thu, 22 Jan 2026 16:54:37 -0800 Subject: [PATCH 2/2] Rename stuff --- .../app/src/main/AndroidManifest.xml | 2 +- .../executorchllamademo/MainActivity.kt | 4 +-- ...gsActivity.kt => ModelSettingsActivity.kt} | 12 ++++----- .../executorchllamademo/WelcomeActivity.kt | 2 +- ...ttingsScreen.kt => ModelSettingsScreen.kt} | 26 +++++++++---------- ...ViewModel.kt => ModelSettingsViewModel.kt} | 10 +++---- 6 files changed, 28 insertions(+), 28 deletions(-) rename llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/{SettingsActivity.kt => ModelSettingsActivity.kt} (88%) rename llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/{SettingsScreen.kt => ModelSettingsScreen.kt} (96%) rename llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/viewmodel/{SettingsViewModel.kt => ModelSettingsViewModel.kt} (93%) diff --git a/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml b/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml index 986414d97e..f06c4dd616 100644 --- a/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml +++ b/llm/android/LlamaDemo/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ android:name=".LogsActivity" android:exported="false" /> chatViewModel?.setAudioFile(audioFile) } diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/SettingsActivity.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ModelSettingsActivity.kt similarity index 88% rename from llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/SettingsActivity.kt rename to llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ModelSettingsActivity.kt index 80b7fd81d8..f015f4c8d6 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/SettingsActivity.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ModelSettingsActivity.kt @@ -22,12 +22,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.core.content.ContextCompat import androidx.lifecycle.viewmodel.compose.viewModel -import com.example.executorchllamademo.ui.screens.SettingsScreen +import com.example.executorchllamademo.ui.screens.ModelSettingsScreen import com.example.executorchllamademo.ui.theme.LlamaDemoTheme -import com.example.executorchllamademo.ui.viewmodel.SettingsViewModel +import com.example.executorchllamademo.ui.viewmodel.ModelSettingsViewModel import java.io.File -class SettingsActivity : ComponentActivity() { +class ModelSettingsActivity : ComponentActivity() { private var appearanceMode by mutableStateOf(AppearanceMode.SYSTEM) @@ -50,8 +50,8 @@ class SettingsActivity : ComponentActivity() { LlamaDemoTheme(darkTheme = isDarkTheme) { Surface(modifier = Modifier.fillMaxSize()) { - val viewModel: SettingsViewModel = viewModel() - SettingsScreen( + val viewModel: ModelSettingsViewModel = viewModel() + ModelSettingsScreen( viewModel = viewModel, onBackPressed = { viewModel.saveSettings() @@ -59,7 +59,7 @@ class SettingsActivity : ComponentActivity() { }, onLoadModel = { // Navigate to MainActivity (conversation) after loading model - startActivity(Intent(this@SettingsActivity, MainActivity::class.java)) + startActivity(Intent(this@ModelSettingsActivity, MainActivity::class.java)) finish() }, onAppearanceChanged = { mode -> diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt index 4a67040e13..90af656e9b 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/WelcomeActivity.kt @@ -49,7 +49,7 @@ class WelcomeActivity : ComponentActivity() { Surface(modifier = Modifier.fillMaxSize()) { WelcomeScreen( onLoadModelClick = { - startActivity(Intent(this@WelcomeActivity, SettingsActivity::class.java)) + startActivity(Intent(this@WelcomeActivity, ModelSettingsActivity::class.java)) }, onAppSettingsClick = { startActivity(Intent(this@WelcomeActivity, AppSettingsActivity::class.java)) diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/SettingsScreen.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/ModelSettingsScreen.kt similarity index 96% rename from llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/SettingsScreen.kt rename to llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/ModelSettingsScreen.kt index 62f61b7b42..0363a37b3a 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/SettingsScreen.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/screens/ModelSettingsScreen.kt @@ -68,11 +68,11 @@ import com.example.executorchllamademo.ui.components.SettingsRow import com.example.executorchllamademo.ui.theme.BtnDisabled import com.example.executorchllamademo.ui.theme.BtnEnabled import com.example.executorchllamademo.ui.theme.LocalAppColors -import com.example.executorchllamademo.ui.viewmodel.SettingsViewModel +import com.example.executorchllamademo.ui.viewmodel.ModelSettingsViewModel @Composable -fun SettingsScreen( - viewModel: SettingsViewModel = viewModel(), +fun ModelSettingsScreen( + viewModel: ModelSettingsViewModel = viewModel(), onBackPressed: () -> Unit = {}, onLoadModel: () -> Unit = {}, onAppearanceChanged: (AppearanceMode) -> Unit = {} @@ -388,7 +388,7 @@ private fun PromptSection( } @Composable -private fun BackendDialog(viewModel: SettingsViewModel) { +private fun BackendDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showBackendDialog) { SingleChoiceDialog( title = "Select backend type", @@ -404,7 +404,7 @@ private fun BackendDialog(viewModel: SettingsViewModel) { @Composable private fun AppearanceDialog( - viewModel: SettingsViewModel, + viewModel: ModelSettingsViewModel, onAppearanceChanged: (AppearanceMode) -> Unit ) { if (viewModel.showAppearanceDialog) { @@ -423,7 +423,7 @@ private fun AppearanceDialog( } @Composable -private fun ModelDialog(viewModel: SettingsViewModel) { +private fun ModelDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showModelDialog) { if (viewModel.modelFiles.isEmpty()) { AlertDialog( @@ -453,7 +453,7 @@ private fun ModelDialog(viewModel: SettingsViewModel) { } @Composable -private fun TokenizerDialog(viewModel: SettingsViewModel) { +private fun TokenizerDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showTokenizerDialog) { if (viewModel.tokenizerFiles.isEmpty()) { AlertDialog( @@ -483,7 +483,7 @@ private fun TokenizerDialog(viewModel: SettingsViewModel) { } @Composable -private fun DataPathDialog(viewModel: SettingsViewModel) { +private fun DataPathDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showDataPathDialog) { val options = if (viewModel.dataPathFiles.isEmpty()) { listOf("(unused)") @@ -508,7 +508,7 @@ private fun DataPathDialog(viewModel: SettingsViewModel) { } @Composable -private fun ModelTypeDialog(viewModel: SettingsViewModel) { +private fun ModelTypeDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showModelTypeDialog) { SingleChoiceDialog( title = "Select model type", @@ -524,7 +524,7 @@ private fun ModelTypeDialog(viewModel: SettingsViewModel) { @Composable private fun LoadModelDialog( - viewModel: SettingsViewModel, + viewModel: ModelSettingsViewModel, onLoadModel: () -> Unit, onBackPressed: () -> Unit ) { @@ -561,7 +561,7 @@ private fun LoadModelDialog( } @Composable -private fun ResetSystemPromptDialog(viewModel: SettingsViewModel) { +private fun ResetSystemPromptDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showResetSystemPromptDialog) { AlertDialog( onDismissRequest = { viewModel.showResetSystemPromptDialog = false }, @@ -593,7 +593,7 @@ private fun ResetSystemPromptDialog(viewModel: SettingsViewModel) { } @Composable -private fun ResetUserPromptDialog(viewModel: SettingsViewModel) { +private fun ResetUserPromptDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showResetUserPromptDialog) { AlertDialog( onDismissRequest = { viewModel.showResetUserPromptDialog = false }, @@ -625,7 +625,7 @@ private fun ResetUserPromptDialog(viewModel: SettingsViewModel) { } @Composable -private fun InvalidPromptDialog(viewModel: SettingsViewModel) { +private fun InvalidPromptDialog(viewModel: ModelSettingsViewModel) { if (viewModel.showInvalidPromptDialog) { AlertDialog( onDismissRequest = { viewModel.showInvalidPromptDialog = false }, diff --git a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/viewmodel/SettingsViewModel.kt b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/viewmodel/ModelSettingsViewModel.kt similarity index 93% rename from llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/viewmodel/SettingsViewModel.kt rename to llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/viewmodel/ModelSettingsViewModel.kt index 0b321beeea..67c3eb21c7 100644 --- a/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/viewmodel/SettingsViewModel.kt +++ b/llm/android/LlamaDemo/app/src/main/java/com/example/executorchllamademo/ui/viewmodel/ModelSettingsViewModel.kt @@ -17,12 +17,12 @@ import com.example.executorchllamademo.AppearanceMode import com.example.executorchllamademo.AppSettings import com.example.executorchllamademo.BackendType import com.example.executorchllamademo.DemoSharedPreferences +import com.example.executorchllamademo.ModelSettingsActivity import com.example.executorchllamademo.ModelType import com.example.executorchllamademo.ModuleSettings import com.example.executorchllamademo.PromptFormat -import com.example.executorchllamademo.SettingsActivity -class SettingsViewModel : ViewModel() { +class ModelSettingsViewModel : ViewModel() { var moduleSettings by mutableStateOf(ModuleSettings()) private set @@ -71,9 +71,9 @@ class SettingsViewModel : ViewModel() { } fun refreshFileLists() { - modelFiles = SettingsActivity.listLocalFile("/data/local/tmp/llama/", arrayOf(".pte")) - tokenizerFiles = SettingsActivity.listLocalFile("/data/local/tmp/llama/", arrayOf(".bin", ".json", ".model")) - dataPathFiles = SettingsActivity.listLocalFile("/data/local/tmp/llama/", arrayOf(".ptd")) + modelFiles = ModelSettingsActivity.listLocalFile("/data/local/tmp/llama/", arrayOf(".pte")) + tokenizerFiles = ModelSettingsActivity.listLocalFile("/data/local/tmp/llama/", arrayOf(".bin", ".json", ".model")) + dataPathFiles = ModelSettingsActivity.listLocalFile("/data/local/tmp/llama/", arrayOf(".ptd")) } // Backend selection