Skip to content

Commit a461f4d

Browse files
Only display the input fields that are configured in the scope (#631)
* Add option to select about fields in the demo app * Use the aboutFields from the scope config in AboutEditor * Generate new screenshots * Remodel state management for the AboutInputField * Generate quickeditor.api file * Create custom saver for Set<AboutInputField> There's a crash caused by rememberSaveable { mutableStateOf(AboutInputField.all) } The default saver doesn't support non-primitive types. * Only show section labels when fields from both are visible
1 parent 6d758bc commit a461f4d

24 files changed

+600
-381
lines changed

demo-app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ dependencies {
118118
implementation(libs.androidx.activity)
119119
implementation(libs.androidx.constraintlayout)
120120
implementation(libs.ucrop)
121+
implementation(libs.composables.core)
121122
implementation(project(":gravatar"))
122123
implementation(project(":gravatar-ui"))
123124
implementation(project(":gravatar-quickeditor"))

demo-app/src/main/java/com/gravatar/demoapp/ui/AvatarUpdateTab.kt

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.widget.Toast
55
import androidx.compose.animation.AnimatedVisibility
66
import androidx.compose.foundation.ScrollState
77
import androidx.compose.foundation.background
8+
import androidx.compose.foundation.clickable
89
import androidx.compose.foundation.layout.Arrangement
910
import androidx.compose.foundation.layout.Box
1011
import androidx.compose.foundation.layout.Column
@@ -24,6 +25,7 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults
2425
import androidx.compose.material3.MaterialTheme
2526
import androidx.compose.material3.Text
2627
import androidx.compose.material3.TextField
28+
import androidx.compose.material3.TextFieldDefaults
2729
import androidx.compose.runtime.Composable
2830
import androidx.compose.runtime.CompositionLocalProvider
2931
import androidx.compose.runtime.LaunchedEffect
@@ -46,8 +48,10 @@ import androidx.compose.ui.unit.dp
4648
import com.gravatar.demoapp.BuildConfig
4749
import com.gravatar.demoapp.R
4850
import com.gravatar.demoapp.ui.activity.QuickEditorTestActivity
51+
import com.gravatar.demoapp.ui.components.AboutFieldsBottomSheet
4952
import com.gravatar.demoapp.ui.components.GravatarEmailInput
5053
import com.gravatar.demoapp.ui.components.GravatarPasswordInput
54+
import com.gravatar.demoapp.ui.components.translatedValue
5155
import com.gravatar.quickeditor.GravatarQuickEditor
5256
import com.gravatar.quickeditor.ui.editor.AboutEditorConfiguration
5357
import com.gravatar.quickeditor.ui.editor.AboutEditorResult
@@ -81,9 +85,13 @@ fun AvatarUpdateTab(modifier: Modifier = Modifier) {
8185
var tokenVisible by remember { mutableStateOf(false) }
8286
val context = LocalContext.current
8387
var showBottomSheet by rememberSaveable { mutableStateOf(false) }
88+
var showAboutFieldsPicker by rememberSaveable { mutableStateOf(false) }
8489
val coroutineScope = rememberCoroutineScope()
8590
var cacheBuster: String? by remember { mutableStateOf(null) }
8691
val scrollState: ScrollState = rememberScrollState()
92+
var aboutFields: Set<AboutInputField> by rememberSaveable(stateSaver = AboutInputFieldSetSaver) {
93+
mutableStateOf(AboutInputField.all)
94+
}
8795
var pickerContentLayout: AvatarPickerContentLayout by rememberSaveable(
8896
stateSaver = AvatarPickerContentLayoutSaver,
8997
) {
@@ -184,6 +192,27 @@ fun AvatarUpdateTab(modifier: Modifier = Modifier) {
184192
modifier = Modifier.fillMaxWidth(),
185193
)
186194
}
195+
AnimatedVisibility(
196+
visible = editorScope == QuickEditorScope.AvatarAndAbout || editorScope == QuickEditorScope.About,
197+
) {
198+
TextField(
199+
enabled = false,
200+
value = aboutFields.joinToString(", ") { it.translatedValue(context) },
201+
maxLines = 1,
202+
singleLine = true,
203+
onValueChange = { },
204+
colors = TextFieldDefaults.colors(
205+
disabledTextColor = MaterialTheme.colorScheme.onSurface,
206+
disabledLabelColor = MaterialTheme.colorScheme.onSurface,
207+
),
208+
label = { Text("About fields") },
209+
modifier = Modifier
210+
.clickable {
211+
showAboutFieldsPicker = true
212+
}
213+
.fillMaxWidth(),
214+
)
215+
}
187216
Button(
188217
onClick = {
189218
keyboardController?.hide()
@@ -222,6 +251,15 @@ fun AvatarUpdateTab(modifier: Modifier = Modifier) {
222251
}
223252
}
224253
}
254+
if (showAboutFieldsPicker) {
255+
AboutFieldsBottomSheet(
256+
aboutFields = aboutFields,
257+
onDismiss = { showAboutFieldsPicker = false },
258+
onFieldsChanged = { fields ->
259+
aboutFields = fields
260+
},
261+
)
262+
}
225263
if (showBottomSheet) {
226264
val authenticationMethod = if (useToken) {
227265
AuthenticationMethod.Bearer(userToken)
@@ -256,15 +294,15 @@ fun AvatarUpdateTab(modifier: Modifier = Modifier) {
256294

257295
QuickEditorScope.About -> QuickEditorScopeOption.aboutEditor(
258296
config = AboutEditorConfiguration(
259-
fields = AboutInputField.all,
297+
fields = aboutFields,
260298
),
261299
)
262300

263301
QuickEditorScope.AvatarAndAbout -> QuickEditorScopeOption.avatarAndAbout(
264302
config = AvatarPickerAndAboutEditorConfiguration(
265303
contentLayout = pickerContentLayout,
266304
initialPage = editorInitialPage,
267-
fields = AboutInputField.all,
305+
fields = aboutFields,
268306
),
269307
)
270308
}
@@ -466,6 +504,11 @@ private fun InitialPageDropdown(
466504
}
467505
}
468506

507+
private val AboutInputFieldSetSaver = Saver<Set<AboutInputField>, List<AboutInputField>>(
508+
save = { it.toList() },
509+
restore = { it.toSet() },
510+
)
511+
469512
private val AvatarPickerContentLayoutSaver: Saver<AvatarPickerContentLayout, String> = run {
470513
val horizontalKey = "horizontal"
471514
val verticalKey = "vertical"
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package com.gravatar.demoapp.ui.components
2+
3+
import android.content.Context
4+
import androidx.compose.animation.animateColorAsState
5+
import androidx.compose.foundation.background
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.WindowInsets
10+
import androidx.compose.foundation.layout.WindowInsetsSides
11+
import androidx.compose.foundation.layout.asPaddingValues
12+
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.foundation.layout.imePadding
14+
import androidx.compose.foundation.layout.navigationBars
15+
import androidx.compose.foundation.layout.only
16+
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.foundation.layout.widthIn
18+
import androidx.compose.foundation.shape.CircleShape
19+
import androidx.compose.foundation.shape.RoundedCornerShape
20+
import androidx.compose.material3.MaterialTheme
21+
import androidx.compose.material3.Surface
22+
import androidx.compose.material3.Text
23+
import androidx.compose.material3.surfaceColorAtElevation
24+
import androidx.compose.runtime.Composable
25+
import androidx.compose.runtime.getValue
26+
import androidx.compose.ui.Alignment
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.draw.clip
29+
import androidx.compose.ui.draw.shadow
30+
import androidx.compose.ui.platform.LocalContext
31+
import androidx.compose.ui.tooling.preview.Preview
32+
import androidx.compose.ui.unit.dp
33+
import com.composables.core.ModalBottomSheet
34+
import com.composables.core.ModalSheetProperties
35+
import com.composables.core.Scrim
36+
import com.composables.core.Sheet
37+
import com.composables.core.SheetDetent
38+
import com.composables.core.rememberModalBottomSheetState
39+
import com.composeunstyled.Thumb
40+
import com.composeunstyled.ToggleSwitch
41+
import com.gravatar.quickeditor.ui.editor.AboutInputField
42+
43+
@Composable
44+
internal fun AboutFieldsBottomSheet(
45+
aboutFields: Set<AboutInputField>,
46+
onFieldsChanged: (Set<AboutInputField>) -> Unit,
47+
onDismiss: () -> Unit = {},
48+
) {
49+
ModalBottomSheet(
50+
state = rememberModalBottomSheetState(
51+
initialDetent = SheetDetent.FullyExpanded,
52+
),
53+
onDismiss = onDismiss,
54+
properties = ModalSheetProperties(
55+
dismissOnBackPress = true,
56+
dismissOnClickOutside = true,
57+
),
58+
) {
59+
Scrim(
60+
scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.32f),
61+
)
62+
Sheet(
63+
modifier = Modifier
64+
.imePadding()
65+
.clip(RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp))
66+
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
67+
.widthIn(max = 640.dp)
68+
.fillMaxWidth()
69+
.padding(
70+
WindowInsets.navigationBars
71+
.only(WindowInsetsSides.Vertical)
72+
.asPaddingValues(),
73+
),
74+
) {
75+
Surface(
76+
modifier = Modifier
77+
.padding(24.dp)
78+
.fillMaxWidth(),
79+
tonalElevation = 1.dp,
80+
) {
81+
Column(
82+
horizontalAlignment = Alignment.CenterHorizontally,
83+
) {
84+
AboutInputField.all.forEach { field ->
85+
Row(
86+
modifier = Modifier
87+
.padding(4.dp)
88+
.fillMaxWidth(),
89+
) {
90+
val fieldsChecked = aboutFields.contains(field)
91+
92+
val animatedColor by animateColorAsState(
93+
if (fieldsChecked) {
94+
MaterialTheme.colorScheme.primary
95+
} else {
96+
MaterialTheme.colorScheme.inversePrimary
97+
},
98+
)
99+
Text(
100+
text = field.translatedValue(LocalContext.current),
101+
modifier = Modifier
102+
.weight(1f),
103+
)
104+
ToggleSwitch(
105+
toggled = fieldsChecked,
106+
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh,
107+
shape = RoundedCornerShape(100),
108+
contentPadding = PaddingValues(2.dp),
109+
onToggled = { toggled ->
110+
if (toggled) {
111+
onFieldsChanged(aboutFields + field)
112+
} else {
113+
onFieldsChanged(aboutFields - field)
114+
}
115+
},
116+
thumb = {
117+
Thumb(
118+
shape = CircleShape,
119+
color = animatedColor,
120+
modifier = Modifier.shadow(elevation = 4.dp, CircleShape),
121+
)
122+
},
123+
)
124+
}
125+
}
126+
}
127+
}
128+
}
129+
}
130+
}
131+
132+
internal fun AboutInputField.translatedValue(context: Context): String {
133+
return when (this) {
134+
AboutInputField.DisplayName -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_display_name
135+
AboutInputField.AboutMe -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_about_me
136+
AboutInputField.Pronouns -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_pronouns
137+
AboutInputField.Pronunciation -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_pronunciation
138+
AboutInputField.Location -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_location
139+
AboutInputField.JobTitle -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_job_title
140+
AboutInputField.Company -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_company
141+
else -> com.gravatar.quickeditor.R.string.gravatar_qe_about_field_label_display_name
142+
}.let { context.getString(it) }
143+
}
144+
145+
@Preview
146+
@Composable
147+
private fun AboutFieldsBottomSheetPreview() {
148+
AboutFieldsBottomSheet(
149+
aboutFields = setOf(
150+
AboutInputField.DisplayName,
151+
AboutInputField.AboutMe,
152+
AboutInputField.Pronouns,
153+
AboutInputField.Pronunciation,
154+
AboutInputField.Location,
155+
AboutInputField.JobTitle,
156+
AboutInputField.Company,
157+
),
158+
onFieldsChanged = {},
159+
)
160+
}

gravatar-quickeditor/api/gravatar-quickeditor.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ public final class com/gravatar/quickeditor/ui/abouteditor/components/Composable
8484
public final class com/gravatar/quickeditor/ui/abouteditor/components/ComposableSingletons$AboutSectionKt {
8585
public static final field INSTANCE Lcom/gravatar/quickeditor/ui/abouteditor/components/ComposableSingletons$AboutSectionKt;
8686
public static field lambda-1 Lkotlin/jvm/functions/Function2;
87+
public static field lambda-2 Lkotlin/jvm/functions/Function2;
8788
public fun <init> ()V
8889
public final fun getLambda-1$gravatar_quickeditor_release ()Lkotlin/jvm/functions/Function2;
90+
public final fun getLambda-2$gravatar_quickeditor_release ()Lkotlin/jvm/functions/Function2;
8991
}
9092

9193
public final class com/gravatar/quickeditor/ui/abouteditor/components/ComposableSingletons$DiscardChangesAlertDialogKt {

gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/abouteditor/AboutEditor.kt

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import com.gravatar.quickeditor.R
3434
import com.gravatar.quickeditor.ui.abouteditor.components.AboutSection
3535
import com.gravatar.quickeditor.ui.abouteditor.components.DiscardChangesAlertDialog
3636
import com.gravatar.quickeditor.ui.components.QEButton
37+
import com.gravatar.quickeditor.ui.editor.AboutInputField
3738
import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams
3839
import com.gravatar.quickeditor.ui.extensions.QESnackbarHost
3940
import com.gravatar.quickeditor.ui.extensions.SnackbarType
@@ -130,7 +131,7 @@ internal fun AboutEditor(
130131
@Composable
131132
internal fun AboutEditor(
132133
uiState: AboutEditorUiState,
133-
onValueChange: (AboutInputField) -> Unit,
134+
onValueChange: (AboutEditorField) -> Unit,
134135
onSaveClick: () -> Unit,
135136
) {
136137
Surface {
@@ -163,7 +164,7 @@ internal fun AboutEditor(
163164
formEnabled = uiState.formEnabled,
164165
modifier = Modifier
165166
.fillMaxWidth()
166-
.padding(horizontal = 16.dp),
167+
.padding(16.dp),
167168
onValueChange = onValueChange,
168169
)
169170
}
@@ -189,17 +190,36 @@ internal fun AboutEditorLoadedPreview() {
189190
Box(modifier = Modifier.padding(10.dp)) {
190191
AboutEditor(
191192
uiState = AboutEditorUiState(
192-
aboutFields = AboutFields(
193-
personal = PersonalFields(
194-
aboutMe = AboutInputField.Personal.AboutMe(value = "My description"),
195-
displayName = AboutInputField.Personal.DisplayName(value = "John Doe"),
196-
location = AboutInputField.Personal.Location(value = "San Francisco, CA"),
197-
pronunciation = AboutInputField.Personal.Pronunciation(value = "John Doe"),
198-
pronouns = AboutInputField.Personal.Pronouns(value = "he/him"),
193+
aboutFields = setOf(
194+
AboutEditorField(
195+
type = AboutInputField.DisplayName,
196+
value = "John Doe",
197+
maxLines = 1,
199198
),
200-
professional = ProfessionalFields(
201-
company = AboutInputField.Professional.Company(value = "Automattic"),
202-
jobTitle = AboutInputField.Professional.JobTitle(value = "Software Engineer"),
199+
AboutEditorField(
200+
type = AboutInputField.AboutMe,
201+
value = "My description",
202+
maxLines = 3,
203+
),
204+
AboutEditorField(
205+
type = AboutInputField.Pronunciation,
206+
value = "John Doe",
207+
),
208+
AboutEditorField(
209+
type = AboutInputField.Pronouns,
210+
value = "he/him",
211+
),
212+
AboutEditorField(
213+
type = AboutInputField.Location,
214+
value = "San Francisco, CA",
215+
),
216+
AboutEditorField(
217+
type = AboutInputField.Company,
218+
value = "Automattic",
219+
),
220+
AboutEditorField(
221+
type = AboutInputField.JobTitle,
222+
value = "Software Engineer",
203223
),
204224
),
205225
),
@@ -217,7 +237,7 @@ internal fun AboutEditorLoadingPreview() {
217237
Box(modifier = Modifier.padding(10.dp)) {
218238
AboutEditor(
219239
uiState = AboutEditorUiState(
220-
aboutFields = AboutFields.EMPTY,
240+
aboutFields = emptySet(),
221241
isLoading = true,
222242
),
223243
onValueChange = { },

gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/abouteditor/AboutEditorEvent.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.gravatar.quickeditor.ui.abouteditor
22

33
internal sealed class AboutEditorEvent {
44
data class OnAboutFieldUpdated(
5-
val aboutField: AboutInputField,
5+
val aboutField: AboutEditorField,
66
) : AboutEditorEvent()
77

88
data object OnSaveClicked : AboutEditorEvent()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.gravatar.quickeditor.ui.abouteditor
2+
3+
import com.gravatar.quickeditor.ui.editor.AboutInputField
4+
5+
internal data class AboutEditorField(
6+
val type: AboutInputField,
7+
val value: String,
8+
val maxLines: Int = 1,
9+
)

0 commit comments

Comments
 (0)