diff --git a/examples/llama.android/app/src/main/java/com/example/llama/MainActivity.kt b/examples/llama.android/app/src/main/java/com/example/llama/MainActivity.kt index 020a74665e..dad1849e24 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/MainActivity.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/MainActivity.kt @@ -94,8 +94,9 @@ fun AppContent( val coroutineScope = rememberCoroutineScope() val snackbarHostState = remember { SnackbarHostState() } - // Inference engine state + // App core states val engineState by mainViewModel.engineState.collectAsState() + val showUserOnboarding by mainViewModel.showUserOnboarding.collectAsState() // Model state val modelScreenUiMode by modelsViewModel.modelScreenUiMode.collectAsState() @@ -323,7 +324,7 @@ fun AppContent( toggleMenu = modelsViewModel::toggleFilterMenu ), importing = BottomBarConfig.Models.Managing.ImportConfig( - showTooltip = true, + showTooltip = showUserOnboarding, isMenuVisible = showImportModelMenu, toggleMenu = { show -> modelsManagementViewModel.toggleImportMenu(show) }, importFromLocal = { diff --git a/examples/llama.android/app/src/main/java/com/example/llama/data/source/prefs/AppPreferences.kt b/examples/llama.android/app/src/main/java/com/example/llama/data/source/prefs/AppPreferences.kt new file mode 100644 index 0000000000..2d9e827a30 --- /dev/null +++ b/examples/llama.android/app/src/main/java/com/example/llama/data/source/prefs/AppPreferences.kt @@ -0,0 +1,67 @@ +package com.example.llama.data.source.prefs + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Manages internal preferences for the application. + */ +@Singleton +class AppPreferences @Inject constructor ( + @ApplicationContext private val context: Context +) { + companion object { + private const val DATASTORE_APP = "app" + private val Context.appDataStore: DataStore + by preferencesDataStore(name = DATASTORE_APP) + + // Preference keys + private val USER_HAS_IMPORTED_FIRST_MODEL = booleanPreferencesKey("user_has_imported_first_model") + private val USER_HAS_CHATTED_WITH_MODEL = booleanPreferencesKey("user_has_chatted_with_model") + } + + /** + * Gets whether the user has imported his first model + */ + fun userHasImportedFirstModel(): Flow = + context.appDataStore.data.map { preferences -> + preferences[USER_HAS_IMPORTED_FIRST_MODEL] == true + } + + /** + * Sets whether the user has completed importing the first model. + */ + suspend fun setUserHasImportedFirstModel(done: Boolean) = withContext(Dispatchers.IO) { + context.appDataStore.edit { preferences -> + preferences[USER_HAS_IMPORTED_FIRST_MODEL] = done + } + } + + /** + * Gets whether the user has chatted with a model + */ + fun userHasChattedWithModel(): Flow = + context.appDataStore.data.map { preferences -> + preferences[USER_HAS_CHATTED_WITH_MODEL] == true + } + + /** + * Sets whether the user has completed chatting with a model. + */ + suspend fun setUserHasChattedWithModel(done: Boolean) = withContext(Dispatchers.IO) { + context.appDataStore.edit { preferences -> + preferences[USER_HAS_CHATTED_WITH_MODEL] = done + } + } +} diff --git a/examples/llama.android/app/src/main/java/com/example/llama/data/source/prefs/UserPreferences.kt b/examples/llama.android/app/src/main/java/com/example/llama/data/source/prefs/UserPreferences.kt index a1f72c3fe7..3ec68eef05 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/data/source/prefs/UserPreferences.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/data/source/prefs/UserPreferences.kt @@ -9,8 +9,10 @@ import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.preferencesDataStore import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @@ -23,11 +25,11 @@ class UserPreferences @Inject constructor ( ) { companion object { - // Performance monitoring preferences private const val DATASTORE_SETTINGS = "settings" private val Context.settingsDataStore: DataStore by preferencesDataStore(name = DATASTORE_SETTINGS) + // Preferences keys private val PERFORMANCE_MONITORING_ENABLED = booleanPreferencesKey("performance_monitoring_enabled") private val USE_FAHRENHEIT_TEMPERATURE = booleanPreferencesKey("use_fahrenheit_temperature") private val MONITORING_INTERVAL_MS = longPreferencesKey("monitoring_interval_ms") @@ -45,16 +47,15 @@ class UserPreferences @Inject constructor ( /** * Gets whether performance monitoring is enabled. */ - fun isPerformanceMonitoringEnabled(): Flow { - return context.settingsDataStore.data.map { preferences -> + fun isPerformanceMonitoringEnabled(): Flow = + context.settingsDataStore.data.map { preferences -> preferences[PERFORMANCE_MONITORING_ENABLED] != false } - } /** * Sets whether performance monitoring is enabled. */ - suspend fun setPerformanceMonitoringEnabled(enabled: Boolean) { + suspend fun setPerformanceMonitoringEnabled(enabled: Boolean) = withContext(Dispatchers.IO) { context.settingsDataStore.edit { preferences -> preferences[PERFORMANCE_MONITORING_ENABLED] = enabled } @@ -63,16 +64,15 @@ class UserPreferences @Inject constructor ( /** * Gets whether temperature should be displayed in Fahrenheit. */ - fun usesFahrenheitTemperature(): Flow { - return context.settingsDataStore.data.map { preferences -> + fun usesFahrenheitTemperature(): Flow = + context.settingsDataStore.data.map { preferences -> preferences[USE_FAHRENHEIT_TEMPERATURE] == true } - } /** * Sets whether temperature should be displayed in Fahrenheit. */ - suspend fun setUseFahrenheitTemperature(useFahrenheit: Boolean) { + suspend fun setUseFahrenheitTemperature(useFahrenheit: Boolean) = withContext(Dispatchers.IO) { context.settingsDataStore.edit { preferences -> preferences[USE_FAHRENHEIT_TEMPERATURE] = useFahrenheit } @@ -83,16 +83,15 @@ class UserPreferences @Inject constructor ( * * TODO-han.yin: replace with Enum value instead of millisecond value */ - fun getMonitoringInterval(): Flow { - return context.settingsDataStore.data.map { preferences -> + fun getMonitoringInterval(): Flow = + context.settingsDataStore.data.map { preferences -> preferences[MONITORING_INTERVAL_MS] ?: DEFAULT_MONITORING_INTERVAL_MS } - } /** * Sets the monitoring interval in milliseconds. */ - suspend fun setMonitoringInterval(intervalMs: Long) { + suspend fun setMonitoringInterval(intervalMs: Long) = withContext(Dispatchers.IO) { context.settingsDataStore.edit { preferences -> preferences[MONITORING_INTERVAL_MS] = intervalMs } @@ -101,16 +100,15 @@ class UserPreferences @Inject constructor ( /** * Gets the current theme mode. */ - fun getThemeMode(): Flow { - return context.settingsDataStore.data.map { preferences -> + fun getThemeMode(): Flow = + context.settingsDataStore.data.map { preferences -> preferences[THEME_MODE] ?: THEME_MODE_AUTO } - } /** * Sets the theme mode. */ - suspend fun setThemeMode(mode: Int) { + suspend fun setThemeMode(mode: Int) = withContext(Dispatchers.IO) { context.settingsDataStore.edit { preferences -> preferences[THEME_MODE] = mode } diff --git a/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/MainViewModel.kt b/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/MainViewModel.kt index 0106341e50..052a8a9cb4 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/MainViewModel.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/MainViewModel.kt @@ -1,23 +1,65 @@ package com.example.llama.viewmodel import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.llama.data.source.prefs.AppPreferences import com.example.llama.engine.InferenceService import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel /** - * Main ViewModel that expose the core states of [InferenceEngine] + * Main ViewModel that expose the core states of [InferenceService] and [AppPreferences] */ class MainViewModel @Inject constructor ( + private val appPreferences: AppPreferences, private val inferenceService: InferenceService, ) : ViewModel() { val engineState = inferenceService.engineState + // App preferences + private val _showModelImportTooltip = MutableStateFlow(true) + val showModelImportTooltip: StateFlow = _showModelImportTooltip.asStateFlow() + + private val _showChatTooltip = MutableStateFlow(true) + val showChatTooltip: StateFlow = _showChatTooltip.asStateFlow() + + /** * Unload the current model and release the resources */ suspend fun unloadModel() = inferenceService.unloadModel() -} + init { + viewModelScope.launch { + launch { + appPreferences.userHasImportedFirstModel().collect { + _showModelImportTooltip.value = !it + } + } + launch { + appPreferences.userHasChattedWithModel().collect { + _showChatTooltip.value = !it + } + } + } + } + + fun waiveModelImportTooltip() { + android.util.Log.w("JOJO", "WAIVE IMPORT TOOLTIP!") + viewModelScope.launch { + appPreferences.setUserHasImportedFirstModel(true) + } + } + + fun waiveChatTooltip() { + viewModelScope.launch { + appPreferences.setUserHasChattedWithModel(true) + } + } +} diff --git a/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/SettingsViewModel.kt b/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/SettingsViewModel.kt index dbf566a4ae..6a102fa88a 100644 --- a/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/SettingsViewModel.kt +++ b/examples/llama.android/app/src/main/java/com/example/llama/viewmodel/SettingsViewModel.kt @@ -4,8 +4,8 @@ import android.llama.cpp.LLamaTier import android.llama.cpp.TierDetection import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.example.llama.data.source.prefs.UserPreferences import com.example.llama.data.repo.ModelRepository +import com.example.llama.data.source.prefs.UserPreferences import com.example.llama.monitoring.BatteryMetrics import com.example.llama.monitoring.MemoryMetrics import com.example.llama.monitoring.PerformanceMonitor