1+ package app.grapheneos.camera.ui.composable.components
2+
3+ import androidx.compose.foundation.ExperimentalFoundationApi
4+ import androidx.compose.foundation.layout.Arrangement
5+ import androidx.compose.foundation.layout.Box
6+
7+ import androidx.compose.foundation.layout.Row
8+ import androidx.compose.foundation.layout.fillMaxWidth
9+
10+ import androidx.compose.foundation.layout.width
11+ import androidx.compose.material.icons.Icons
12+ import androidx.compose.material.icons.filled.MoreVert
13+
14+ import androidx.compose.material3.DropdownMenu
15+ import androidx.compose.material3.DropdownMenuItem
16+ import androidx.compose.material3.ExperimentalMaterial3Api
17+ import androidx.compose.material3.Icon
18+ import androidx.compose.material3.IconButton
19+ import androidx.compose.material3.Text
20+
21+ import androidx.compose.runtime.Composable
22+ import androidx.compose.runtime.getValue
23+ import androidx.compose.runtime.mutableStateOf
24+ import androidx.compose.runtime.remember
25+ import androidx.compose.runtime.setValue
26+ import androidx.compose.ui.Alignment
27+
28+ import androidx.compose.ui.Modifier
29+ import androidx.compose.ui.graphics.Color
30+ import androidx.compose.ui.graphics.vector.ImageVector
31+ import androidx.compose.ui.layout.onGloballyPositioned
32+
33+ import androidx.compose.ui.text.font.FontWeight
34+
35+ import androidx.compose.ui.unit.IntSize
36+
37+ import androidx.compose.ui.unit.dp
38+ import androidx.compose.ui.unit.sp
39+ import app.grapheneos.camera.ktx.toPx
40+
41+ data class TopBarAction (
42+ val id : Any ,
43+ val title : String ,
44+ val icon : ImageVector ,
45+ val alwaysInMoreOptions : Boolean = false
46+ )
47+
48+ private const val TAG = " TopBarActions"
49+
50+ // 40.0.dp was taken from an constant internal to the material library
51+ // IconButtonTokens.StateLayerSize used by IconButton's code internally
52+ private val SPACE_PER_ICON_BUTTON = 40 .dp
53+
54+ @OptIn(ExperimentalMaterial3Api ::class , ExperimentalFoundationApi ::class )
55+ @Composable
56+ fun TopBarActions (
57+ actions : Iterable <TopBarAction >,
58+ onActionClicked : (id: Any ) -> Unit ,
59+ modifier : Modifier = Modifier ,
60+ ) {
61+
62+ var size by remember {
63+ mutableStateOf(IntSize .Zero )
64+ }
65+
66+ var dropDownMenuExpanded by remember {
67+ mutableStateOf(false )
68+ }
69+
70+ Box (
71+ modifier = Modifier
72+ .then(modifier)
73+ .onGloballyPositioned {
74+ size = it.size
75+ },
76+ contentAlignment = Alignment .CenterEnd
77+ ) {
78+ if (size == IntSize .Zero ) return @Box
79+
80+ val width = size.width
81+
82+ // Subtract 1 for the more options icon
83+ val maxVisibleActions = (width / SPACE_PER_ICON_BUTTON .toPx()).toInt() - 1
84+
85+ // Ensure there is space to at least have the more options icon
86+ assert (maxVisibleActions >= 0 )
87+ { " Please ensure that TopBarActions gets a width of 40.dp at least" }
88+
89+ val visibleActions = arrayListOf<TopBarAction >()
90+ val moreOptionsActions = arrayListOf<TopBarAction >()
91+
92+ for (action in actions) {
93+ if (action.alwaysInMoreOptions || visibleActions.size >= maxVisibleActions) {
94+ moreOptionsActions.add(action)
95+ } else {
96+ visibleActions.add(action)
97+ }
98+ }
99+
100+ Row (
101+ horizontalArrangement = Arrangement .End ,
102+ modifier = Modifier .fillMaxWidth()
103+ ) {
104+ for (visibleAction in visibleActions) {
105+ QuickTooltip (
106+ message = visibleAction.title
107+ ) {
108+ IconButton (onClick = {
109+ onActionClicked(visibleAction.id)
110+ }) {
111+ Icon (visibleAction.icon, visibleAction.title)
112+ }
113+ }
114+
115+ }
116+
117+ Box {
118+ if (moreOptionsActions.isNotEmpty()) {
119+ QuickTooltip (
120+ message = " More Options"
121+ ) {
122+ IconButton (onClick = {
123+ dropDownMenuExpanded = ! dropDownMenuExpanded
124+ }) {
125+ Icon (
126+ Icons .Filled .MoreVert ,
127+ " More Options"
128+ )
129+ }
130+ }
131+
132+ DropdownMenu (
133+ expanded = dropDownMenuExpanded,
134+ onDismissRequest = {
135+ dropDownMenuExpanded = false
136+ },
137+ modifier = Modifier .width(200 .dp)
138+ ) {
139+ for (moreOptionsAction in moreOptionsActions) {
140+ DropdownMenuItem (
141+ text = {
142+ Text (
143+ text = moreOptionsAction.title,
144+ color = Color .White ,
145+ fontSize = 16 .sp,
146+ fontWeight = FontWeight .Normal ,
147+ )
148+ },
149+
150+ leadingIcon = {
151+ Icon (
152+ imageVector = moreOptionsAction.icon,
153+ contentDescription = moreOptionsAction.title,
154+ tint = Color .White ,
155+ )
156+ },
157+
158+ onClick = {
159+ onActionClicked(moreOptionsAction.id)
160+ dropDownMenuExpanded = false
161+ },
162+ )
163+ }
164+ }
165+ }
166+ }
167+ }
168+
169+
170+ }
171+
172+ }
0 commit comments