Show tooltips for all app bar buttons

Compose doesn't do the usual long press button to see what it does. So we introduce our own TopAppBarButton to take on this job and in the process also reduce the code we have in the app bars.
This commit is contained in:
Torsten Grote
2026-02-11 17:01:37 -03:00
parent d5dbde32d2
commit 303650ff1c
10 changed files with 156 additions and 158 deletions

View File

@@ -16,7 +16,6 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@@ -47,6 +46,7 @@ import org.fdroid.install.InstallConfirmationState
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.search.TopSearchBar
import org.fdroid.ui.utils.BigLoadingIndicator
import org.fdroid.ui.utils.TopAppBarButton
import org.fdroid.ui.utils.getMyAppsInfo
import org.fdroid.ui.utils.myAppsModel
@@ -97,19 +97,17 @@ fun MyApps(
Text(stringResource(R.string.menu_apps_my))
},
actions = {
IconButton(onClick = { searchActive = true }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = stringResource(R.string.menu_search),
)
}
TopAppBarButton(
imageVector = Icons.Filled.Search,
contentDescription = stringResource(R.string.menu_search),
onClick = { searchActive = true },
)
var sortByMenuExpanded by remember { mutableStateOf(false) }
IconButton(onClick = { sortByMenuExpanded = !sortByMenuExpanded }) {
Icon(
imageVector = Icons.AutoMirrored.Default.Sort,
contentDescription = stringResource(R.string.more),
)
}
TopAppBarButton(
imageVector = Icons.AutoMirrored.Default.Sort,
contentDescription = stringResource(R.string.sort_search),
onClick = { sortByMenuExpanded = !sortByMenuExpanded },
)
DropdownMenu(
expanded = sortByMenuExpanded,
onDismissRequest = { sortByMenuExpanded = false },
@@ -147,20 +145,16 @@ fun MyApps(
},
)
}
IconButton(onClick = { onNav(NavigationKey.InstallationHistory) }) {
Icon(
imageVector = Icons.Default.History,
contentDescription = stringResource(R.string.install_history),
)
}
if (myAppsModel.installedApps != null) {
IconButton(onClick = myAppsInfo.actions::exportInstalledApps) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.menu_share),
)
}
}
TopAppBarButton(
imageVector = Icons.Default.History,
contentDescription = stringResource(R.string.install_history),
onClick = { onNav(NavigationKey.InstallationHistory) },
)
if (myAppsModel.installedApps != null) TopAppBarButton(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.menu_share),
onClick = myAppsInfo.actions::exportInstalledApps,
)
},
scrollBehavior = scrollBehavior,
)

View File

@@ -8,8 +8,6 @@ import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
@@ -25,6 +23,7 @@ import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.launch
import org.fdroid.R
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.utils.TopAppBarButton
import org.fdroid.utils.getLogName
@Composable
@@ -54,12 +53,11 @@ fun Crash(
TopAppBar(
title = {},
actions = {
IconButton(onClick = { launcher.launch("${getLogName(context)}.json") }) {
Icon(
imageVector = Icons.Default.Save,
contentDescription = stringResource(R.string.crash_report_save),
)
}
TopAppBarButton(
imageVector = Icons.Default.Save,
contentDescription = stringResource(R.string.crash_report_save),
onClick = { launcher.launch("${getLogName(context)}.json") },
)
}
)
},

View File

@@ -1,12 +1,9 @@
package org.fdroid.ui.details
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
@@ -22,6 +19,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import org.fdroid.R
import org.fdroid.ui.utils.BackButton
import org.fdroid.ui.utils.TopAppBarButton
import org.fdroid.ui.utils.startActivitySafe
@OptIn(ExperimentalMaterial3Api::class)
@@ -42,30 +41,23 @@ fun AppDetailsTopAppBar(
}
},
navigationIcon = {
if (onBackNav != null) IconButton(onClick = onBackNav) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back),
)
}
if (onBackNav != null) BackButton(onClick = onBackNav)
},
actions = {
val context = LocalContext.current
item.actions.shareIntent?.let { shareIntent ->
IconButton(onClick = { context.startActivitySafe(shareIntent) }) {
Icon(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.menu_share),
)
}
}
var expanded by remember { mutableStateOf(false) }
IconButton(onClick = { expanded = !expanded }) {
Icon(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(R.string.more),
TopAppBarButton(
imageVector = Icons.Filled.Share,
contentDescription = stringResource(R.string.menu_share),
onClick = { context.startActivitySafe(shareIntent) },
)
}
var expanded by remember { mutableStateOf(false) }
TopAppBarButton(
imageVector = Icons.Filled.MoreVert,
contentDescription = stringResource(R.string.more),
onClick = { expanded = !expanded },
)
AppDetailsMenu(item, expanded) { expanded = false }
},
scrollBehavior = scrollBehavior,

View File

@@ -12,8 +12,6 @@ import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
@@ -40,6 +38,7 @@ import org.fdroid.ui.lists.AppListType
import org.fdroid.ui.navigation.NavigationKey
import org.fdroid.ui.navigation.topBarMenuItems
import org.fdroid.ui.utils.BigLoadingIndicator
import org.fdroid.ui.utils.TopAppBarButton
@Composable
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -67,21 +66,19 @@ fun Discover(
modifier = Modifier.size(8.dp)
)
}) {
IconButton(onClick = { onNav(dest.id) }) {
Icon(
imageVector = dest.icon,
contentDescription = stringResource(dest.label),
)
}
TopAppBarButton(
imageVector = dest.icon,
contentDescription = stringResource(dest.label),
onClick = { onNav(dest.id) },
)
}
}
var menuExpanded by remember { mutableStateOf(false) }
IconButton(onClick = { menuExpanded = !menuExpanded }) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.more),
)
}
TopAppBarButton(
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(R.string.more),
onClick = { menuExpanded = !menuExpanded },
)
DiscoverOverFlowMenu(menuExpanded, {
menuExpanded = false
onNav(it.id)

View File

@@ -2,12 +2,9 @@ package org.fdroid.ui.history
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
@@ -26,7 +23,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import org.fdroid.R
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.utils.BackButton
import org.fdroid.ui.utils.BigLoadingIndicator
import org.fdroid.ui.utils.TopAppBarButton
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@@ -43,26 +42,18 @@ fun History(
topBar = {
TopAppBar(
navigationIcon = {
if (onBackClicked != null) IconButton(onClick = onBackClicked) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.back),
)
}
if (onBackClicked != null) BackButton(onClick = onBackClicked)
},
title = {
Text(stringResource(R.string.install_history))
},
actions = {
if (!items.isNullOrEmpty()) IconButton(
if (!items.isNullOrEmpty()) TopAppBarButton(
imageVector = Icons.Filled.Delete,
contentDescription =
stringResource(R.string.install_history_delete_ally),
onClick = { deleteAllDialogShown = true },
) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription =
stringResource(R.string.install_history_delete_ally),
)
}
)
},
scrollBehavior = scrollBehavior,
)

View File

@@ -17,7 +17,6 @@ import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.FilterList
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Badge
@@ -27,11 +26,16 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition.Companion.Below
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults.enterAlwaysScrollBehavior
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.rememberTooltipState
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -54,8 +58,10 @@ import org.fdroid.R
import org.fdroid.database.AppListSortOrder
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.search.TopSearchBar
import org.fdroid.ui.utils.BackButton
import org.fdroid.ui.utils.BigLoadingIndicator
import org.fdroid.ui.utils.OnboardingCard
import org.fdroid.ui.utils.TopAppBarButton
import org.fdroid.ui.utils.getAppListInfo
import org.fdroid.ui.utils.getHintOverlayColor
@@ -113,41 +119,42 @@ fun AppList(
)
},
navigationIcon = {
IconButton(onClick = {
BackButton(onClick = {
if (searchActive) searchActive = false else onBackClicked()
}) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.back),
)
}
})
},
actions = {
IconButton(onClick = { searchActive = true }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = stringResource(R.string.menu_search),
)
}
IconButton(
onClick = { appListInfo.actions.toggleFilterVisibility() },
modifier = Modifier.hintAnchor(
state = hintAnchor,
shape = RoundedCornerShape(16.dp),
)
TopAppBarButton(
imageVector = Icons.Filled.Search,
contentDescription = stringResource(R.string.menu_search),
onClick = { searchActive = true },
)
TooltipBox(
positionProvider =
TooltipDefaults.rememberTooltipPositionProvider(Below),
tooltip = { PlainTooltip { Text(stringResource(R.string.filter)) } },
state = rememberTooltipState(),
) {
val showFilterBadge =
appListInfo.model.filteredRepositoryIds.isNotEmpty() ||
appListInfo.model.filteredCategoryIds.isNotEmpty()
BadgedBox(badge = {
if (showFilterBadge) Badge(
containerColor = MaterialTheme.colorScheme.secondary,
)
}) {
Icon(
imageVector = Icons.Filled.FilterList,
contentDescription = stringResource(R.string.filter),
IconButton(
onClick = { appListInfo.actions.toggleFilterVisibility() },
modifier = Modifier.hintAnchor(
state = hintAnchor,
shape = RoundedCornerShape(16.dp),
)
) {
val showFilterBadge =
appListInfo.model.filteredRepositoryIds.isNotEmpty() ||
appListInfo.model.filteredCategoryIds.isNotEmpty()
BadgedBox(badge = {
if (showFilterBadge) Badge(
containerColor = MaterialTheme.colorScheme.secondary,
)
}) {
Icon(
imageVector = Icons.Filled.FilterList,
contentDescription = stringResource(R.string.filter),
)
}
}
}
},
@@ -160,7 +167,9 @@ fun AppList(
LazyListState()
}
Column(
modifier = Modifier.fillMaxSize().imePadding()
modifier = Modifier
.fillMaxSize()
.imePadding()
) {
val apps = appListInfo.model.apps
if (apps == null) BigLoadingIndicator()

View File

@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.QrCode
@@ -40,9 +39,11 @@ import com.viktormykhailiv.compose.hints.rememberHintController
import org.fdroid.R
import org.fdroid.repo.RepoUpdateWorker
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.utils.BackButton
import org.fdroid.ui.utils.BigLoadingIndicator
import org.fdroid.ui.utils.MeteredConnectionDialog
import org.fdroid.ui.utils.OnboardingCard
import org.fdroid.ui.utils.TopAppBarButton
import org.fdroid.ui.utils.getHintOverlayColor
import org.fdroid.ui.utils.getRepoDetailsInfo
@@ -103,28 +104,21 @@ fun RepoDetails(
Scaffold(topBar = {
TopAppBar(
navigationIcon = {
if (onBackNav != null) IconButton(onClick = onBackNav) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back),
)
}
if (onBackNav != null) BackButton(onClick = onBackNav)
},
title = { },
actions = {
if (repo == null) return@TopAppBar
IconButton(onClick = { info.model.shareRepo(context) }) {
Icon(
imageVector = Icons.Default.Share,
contentDescription = stringResource(R.string.share_repository)
)
}
IconButton(onClick = { qrCodeDialog = true }) {
Icon(
imageVector = Icons.Default.QrCode,
contentDescription = stringResource(R.string.show_repository_qr)
)
}
TopAppBarButton(
imageVector = Icons.Default.Share,
contentDescription = stringResource(R.string.share_repository),
onClick = { info.model.shareRepo(context) },
)
TopAppBarButton(
imageVector = Icons.Default.QrCode,
contentDescription = stringResource(R.string.show_repository_qr),
onClick = { qrCodeDialog = true },
)
IconButton(
enabled = info.model.isUpdateButtonEnabled,
onClick = {

View File

@@ -3,11 +3,7 @@ package org.fdroid.ui.search
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.rememberSearchBarState
import androidx.compose.runtime.Composable
@@ -17,9 +13,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.FlowPreview
import org.fdroid.R
import org.fdroid.ui.utils.BackButton
/**
* This is a top app bar that isn't mean to ever expand with results, but for in-list filtering.
@@ -37,12 +32,7 @@ fun TopSearchBar(
val keyboardController = LocalSoftwareKeyboardController.current
TopAppBar(
navigationIcon = {
IconButton(onClick = onHideSearch) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.back),
)
}
BackButton(onClick = onHideSearch)
},
title = {
AppSearchInputField(

View File

@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.BrightnessMedium
import androidx.compose.material.icons.filled.ColorLens
import androidx.compose.material.icons.filled.Lan
@@ -29,7 +28,6 @@ import androidx.compose.material.icons.filled.UpdateDisabled
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
@@ -79,6 +77,7 @@ import org.fdroid.settings.SettingsConstants.PREF_KEY_THEME
import org.fdroid.settings.toAutoUpdateValue
import org.fdroid.settings.toMirrorChooserValue
import org.fdroid.ui.FDroidContent
import org.fdroid.ui.utils.BackButton
import org.fdroid.ui.utils.asRelativeTimeString
import org.fdroid.ui.utils.startActivitySafe
import org.fdroid.utils.getLogName
@@ -96,12 +95,7 @@ fun Settings(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(onClick = onBackClicked) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.back),
)
}
BackButton(onClick = onBackClicked)
},
title = {
Text(stringResource(R.string.menu_settings))

View File

@@ -0,0 +1,39 @@
package org.fdroid.ui.utils
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition.Companion.Below
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import org.fdroid.R
@Composable
fun TopAppBarButton(imageVector: ImageVector, contentDescription: String, onClick: () -> Unit) {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(Below),
tooltip = { PlainTooltip { Text(contentDescription) } },
state = rememberTooltipState(),
) {
IconButton(onClick = onClick) {
Icon(
imageVector = imageVector,
contentDescription = contentDescription,
)
}
}
}
@Composable
fun BackButton(onClick: () -> Unit) = TopAppBarButton(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back),
onClick = onClick,
)