UI: address Rojin's UX feedbacks

This commit is contained in:
Han Yin 2025-09-01 20:27:49 -07:00
parent 6fb4a94cc3
commit e067f7051b
9 changed files with 78 additions and 10 deletions

View File

@ -99,6 +99,7 @@ fun AppContent(
val engineState by mainViewModel.engineState.collectAsState()
val showModelImportTooltip by mainViewModel.showModelImportTooltip.collectAsState()
val showChatTooltip by mainViewModel.showChatTooltip.collectAsState()
val showManagementTooltip by mainViewModel.showModelManagementTooltip.collectAsState()
// Model state
val modelScreenUiMode by modelsViewModel.modelScreenUiMode.collectAsState()
@ -223,9 +224,11 @@ fun AppContent(
modelsViewModel.resetPreselection()
openDrawer()
},
showTooltip = showManagementTooltip && !showChatTooltip && hasModelsInstalled,
showManagingToggle = !showChatTooltip && hasModelsInstalled,
onToggleManaging = {
if (hasModelsInstalled) {
mainViewModel.waiveModelManagementTooltip()
modelsViewModel.toggleMode(ModelScreenUiMode.MANAGING)
}
},

View File

@ -29,6 +29,7 @@ class AppPreferences @Inject constructor (
// 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")
private val USER_HAS_NAVIGATED_TO_MANAGEMENT = booleanPreferencesKey("user_has_navigated_to_management")
}
/**
@ -64,4 +65,21 @@ class AppPreferences @Inject constructor (
preferences[USER_HAS_CHATTED_WITH_MODEL] = done
}
}
/**
* Gets whether the user has navigated to model management screen.
*/
fun userHasNavigatedToManagement(): Flow<Boolean> =
context.appDataStore.data.map { preferences ->
preferences[USER_HAS_NAVIGATED_TO_MANAGEMENT] == true
}
/**
* Sets whether the user has navigated to model management screen.
*/
suspend fun setUserHasNavigatedToManagement(done: Boolean) = withContext(Dispatchers.IO) {
context.appDataStore.edit { preferences ->
preferences[USER_HAS_NAVIGATED_TO_MANAGEMENT] = done
}
}
}

View File

@ -71,6 +71,7 @@ fun AppScaffold(
is TopBarConfig.ModelsBrowsing -> ModelsBrowsingTopBar(
title = topBarconfig.title,
showTooltip = topBarconfig.showTooltip,
showManagingToggle = topBarconfig.showManagingToggle,
onToggleManaging = topBarconfig.onToggleManaging,
onNavigateBack = topBarconfig.navigationIcon.backAction,

View File

@ -207,7 +207,7 @@ fun ModelsManagementBottomBar(
onDismissRequest = { importingConfig.toggleMenu(false) }
) {
DropdownMenuItem(
text = { Text("Import a local GGUF model") },
text = { Text("Import a local model") },
leadingIcon = {
Icon(
imageVector = Icons.Default.FolderOpen,
@ -217,7 +217,7 @@ fun ModelsManagementBottomBar(
onClick = importingConfig.importFromLocal
)
DropdownMenuItem(
text = { Text("Download from HuggingFace") },
text = { Text("Download a model from Hugging Face") },
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.logo_huggingface),

View File

@ -12,10 +12,16 @@ import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@ -24,11 +30,23 @@ import androidx.compose.ui.unit.dp
@Composable
fun ModelsBrowsingTopBar(
title: String,
showTooltip: Boolean,
showManagingToggle: Boolean,
onToggleManaging: () -> Unit,
onNavigateBack: (() -> Unit)? = null,
onMenuOpen: (() -> Unit)? = null,
) {
val tooltipState = rememberTooltipState(
initialIsVisible = showTooltip,
isPersistent = showTooltip
)
LaunchedEffect(showTooltip, showManagingToggle) {
if (showTooltip && showManagingToggle) {
tooltipState.show()
}
}
TopAppBar(
title = { Text(title) },
navigationIcon = {
@ -54,8 +72,20 @@ fun ModelsBrowsingTopBar(
},
actions = {
if (showManagingToggle) {
ModelManageActionToggle(onToggleManaging)
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
TooltipAnchorPosition.Below),
state = tooltipState,
tooltip = {
PlainTooltip {
Text("Tap this button to install another model or manage your models!")
}
},
onDismissRequest = {}
) {
ModelManageActionToggle(onToggleManaging)
}
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface,

View File

@ -28,6 +28,7 @@ sealed class TopBarConfig {
data class ModelsBrowsing(
override val title: String,
override val navigationIcon: NavigationIcon,
val showTooltip: Boolean,
val showManagingToggle: Boolean,
val onToggleManaging: () -> Unit,
) : TopBarConfig()

View File

@ -43,6 +43,7 @@ import androidx.compose.material3.RadioButton
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -250,6 +251,7 @@ fun ModelLoadingScreen(
if (!showedSystemPromptWarning) {
onScaffoldEvent(ScaffoldEvent.ShowSnackbar(
message = "Model may not support system prompt!\nProceed with caution.",
duration = SnackbarDuration.Long,
))
showedSystemPromptWarning = true
}

View File

@ -117,7 +117,7 @@ fun ModelsManagementAndDeletingScreen(
} else if (filteredModels.isEmpty()) {
// Prompt the user to import a model
val title = when (activeFiltersCount) {
0 -> "Import or download,\n have it your way"
0 -> "Choose a model:\nImport locally or download online"
1 -> "No models match\n the selected filter"
else -> "No models match\n the selected filters"
}

View File

@ -29,6 +29,9 @@ class MainViewModel @Inject constructor (
private val _showChatTooltip = MutableStateFlow(true)
val showChatTooltip: StateFlow<Boolean> = _showChatTooltip.asStateFlow()
private val _showModelManagementTooltip = MutableStateFlow(true)
val showModelManagementTooltip: StateFlow<Boolean> = _showModelManagementTooltip.asStateFlow()
/**
* Unload the current model and release the resources
@ -47,12 +50,11 @@ class MainViewModel @Inject constructor (
_showChatTooltip.value = !it
}
}
}
}
fun waiveModelImportTooltip() {
viewModelScope.launch {
appPreferences.setUserHasImportedFirstModel(true)
launch {
appPreferences.userHasNavigatedToManagement().collect {
_showModelManagementTooltip.value = !it
}
}
}
}
@ -61,4 +63,15 @@ class MainViewModel @Inject constructor (
appPreferences.setUserHasChattedWithModel(true)
}
}
fun waiveModelImportTooltip() {
viewModelScope.launch {
appPreferences.setUserHasImportedFirstModel(true)
}
}
fun waiveModelManagementTooltip() {
viewModelScope.launch {
appPreferences.setUserHasNavigatedToManagement(true)
}
}
}