Skip to content

Commit 59a62ea

Browse files
Extract AvatarsSection to separate file
1 parent 3459b90 commit 59a62ea

File tree

2 files changed

+187
-108
lines changed

2 files changed

+187
-108
lines changed

gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/avatarpicker/AvatarPicker.kt

Lines changed: 17 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ import androidx.compose.foundation.layout.height
1111
import androidx.compose.foundation.layout.padding
1212
import androidx.compose.foundation.layout.size
1313
import androidx.compose.foundation.layout.wrapContentSize
14-
import androidx.compose.foundation.lazy.LazyRow
15-
import androidx.compose.foundation.lazy.items
16-
import androidx.compose.foundation.shape.RoundedCornerShape
17-
import androidx.compose.material3.Button
1814
import androidx.compose.material3.CircularProgressIndicator
1915
import androidx.compose.material3.MaterialTheme
2016
import androidx.compose.material3.Snackbar
@@ -27,23 +23,13 @@ import androidx.compose.runtime.Composable
2723
import androidx.compose.runtime.LaunchedEffect
2824
import androidx.compose.runtime.collectAsState
2925
import androidx.compose.runtime.getValue
30-
import androidx.compose.runtime.mutableIntStateOf
31-
import androidx.compose.runtime.mutableStateOf
3226
import androidx.compose.runtime.remember
33-
import androidx.compose.runtime.saveable.rememberSaveable
34-
import androidx.compose.runtime.setValue
3527
import androidx.compose.ui.Alignment
3628
import androidx.compose.ui.Modifier
37-
import androidx.compose.ui.layout.onPlaced
3829
import androidx.compose.ui.platform.LocalContext
39-
import androidx.compose.ui.res.stringResource
40-
import androidx.compose.ui.text.font.FontWeight
4130
import androidx.compose.ui.text.style.TextAlign
42-
import androidx.compose.ui.tooling.preview.Preview
4331
import androidx.compose.ui.tooling.preview.PreviewLightDark
44-
import androidx.compose.ui.unit.IntOffset
4532
import androidx.compose.ui.unit.dp
46-
import androidx.compose.ui.unit.sp
4733
import androidx.core.net.toUri
4834
import androidx.lifecycle.Lifecycle
4935
import androidx.lifecycle.compose.LocalLifecycleOwner
@@ -52,10 +38,9 @@ import androidx.lifecycle.viewmodel.compose.viewModel
5238
import com.gravatar.extensions.defaultProfile
5339
import com.gravatar.quickeditor.R
5440
import com.gravatar.quickeditor.data.repository.IdentityAvatars
41+
import com.gravatar.quickeditor.ui.components.AvatarsSection
5542
import com.gravatar.quickeditor.ui.components.EmailLabel
56-
import com.gravatar.quickeditor.ui.components.MediaPickerPopup
5743
import com.gravatar.quickeditor.ui.components.ProfileCard
58-
import com.gravatar.quickeditor.ui.components.SelectableAvatar
5944
import com.gravatar.quickeditor.ui.editor.AvatarUpdateResult
6045
import com.gravatar.quickeditor.ui.editor.bottomsheet.DEFAULT_PAGE_HEIGHT
6146
import com.gravatar.restapi.models.Avatar
@@ -154,75 +139,23 @@ internal fun AvatarPicker(uiState: AvatarPickerUiState, onAvatarSelected: (Avata
154139
}
155140
}
156141

157-
@Composable
158-
private fun AvatarsSection(
159-
avatars: List<AvatarUi>,
160-
onAvatarSelected: (Avatar) -> Unit,
161-
modifier: Modifier = Modifier,
162-
) {
163-
var popupVisible by rememberSaveable { mutableStateOf(false) }
164-
var popupYOffset by rememberSaveable { mutableIntStateOf(0) }
165-
Box(
166-
modifier = modifier
167-
.border(
168-
width = 1.dp,
169-
color = MaterialTheme.colorScheme.surfaceContainerHighest,
170-
shape = RoundedCornerShape(8.dp),
171-
)
172-
.padding(16.dp),
173-
) {
174-
Column {
175-
Text(
176-
text = stringResource(id = R.string.avatar_picker_title),
177-
fontSize = 22.sp,
178-
fontWeight = FontWeight.Bold,
179-
)
180-
Text(
181-
text = stringResource(R.string.avatar_picker_description),
182-
fontSize = 15.sp,
183-
color = MaterialTheme.colorScheme.tertiary,
184-
modifier = Modifier.padding(top = 4.dp),
185-
)
142+
private const val UCROP_COMPRESSION_QUALITY = 100
186143

187-
LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(vertical = 24.dp)) {
188-
items(items = avatars, key = { it.avatarId }) { avatarModel ->
189-
when (avatarModel) {
190-
is AvatarUi.Uploaded -> SelectableAvatar(
191-
imageUrl = avatarModel.avatar.fullUrl,
192-
isSelected = avatarModel.isSelected,
193-
isLoading = avatarModel.isLoading,
194-
onAvatarClicked = {
195-
onAvatarSelected(avatarModel.avatar)
196-
},
197-
modifier = Modifier.size(96.dp),
198-
)
199-
}
200-
}
201-
}
202-
Button(
203-
modifier = Modifier
204-
.fillMaxWidth()
205-
.onPlaced { layoutCoordinates -> popupYOffset = layoutCoordinates.size.height },
206-
onClick = { popupVisible = true },
207-
shape = RoundedCornerShape(4.dp),
208-
contentPadding = PaddingValues(14.dp),
209-
) {
210-
Text(
211-
text = stringResource(R.string.avatar_picker_upload_image),
212-
style = MaterialTheme.typography.titleMedium,
213-
)
214-
}
215-
}
216-
if (popupVisible) {
217-
MediaPickerPopup(
218-
alignment = Alignment.BottomCenter,
219-
onDismissRequest = { popupVisible = false },
220-
offset = IntOffset(0, -popupYOffset - 30),
221-
onChoosePhotoClick = { popupVisible = false },
222-
onTakePhotoClick = { popupVisible = false },
223-
)
224-
}
144+
private fun ActivityResultLauncher<Intent>.launchAvatarCrop(image: Uri, tempFile: File, context: Context) {
145+
val options = UCrop.Options().apply {
146+
setToolbarColor(Color.BLACK)
147+
setStatusBarColor(Color.BLACK)
148+
setToolbarWidgetColor(Color.WHITE)
149+
setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.NONE)
150+
setCompressionQuality(UCROP_COMPRESSION_QUALITY)
151+
setCircleDimmedLayer(true)
225152
}
153+
launch(
154+
UCrop.of(image, Uri.fromFile(tempFile))
155+
.withAspectRatio(1f, 1f)
156+
.withOptions(options)
157+
.getIntent(context),
158+
)
226159
}
227160

228161
@Composable
@@ -271,31 +204,7 @@ private fun AvatarPickerLoadingPreview() {
271204
identityAvatars = null,
272205
),
273206
onAvatarSelected = { },
274-
)
275-
}
276-
}
277-
278-
@Composable
279-
@Preview(showBackground = true)
280-
private fun AvatarSectionPreview() {
281-
GravatarTheme {
282-
AvatarsSection(
283-
onAvatarSelected = { },
284-
avatars = listOf(
285-
AvatarUi.Uploaded(
286-
avatar = Avatar {
287-
imageUrl = "/image/url"
288-
format = 0
289-
imageId = "1"
290-
rating = "G"
291-
altText = "alt"
292-
isCropped = true
293-
updatedDate = Instant.now()
294-
},
295-
isSelected = true,
296-
isLoading = false,
297-
),
298-
),
207+
onLocalImageSelected = { },
299208
)
300209
}
301210
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package com.gravatar.quickeditor.ui.components
2+
3+
import android.net.Uri
4+
import androidx.activity.compose.rememberLauncherForActivityResult
5+
import androidx.activity.result.PickVisualMediaRequest
6+
import androidx.activity.result.contract.ActivityResultContracts
7+
import androidx.compose.foundation.border
8+
import androidx.compose.foundation.layout.Arrangement
9+
import androidx.compose.foundation.layout.Box
10+
import androidx.compose.foundation.layout.Column
11+
import androidx.compose.foundation.layout.PaddingValues
12+
import androidx.compose.foundation.layout.fillMaxWidth
13+
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.layout.size
15+
import androidx.compose.foundation.lazy.LazyRow
16+
import androidx.compose.foundation.lazy.items
17+
import androidx.compose.foundation.lazy.rememberLazyListState
18+
import androidx.compose.foundation.shape.RoundedCornerShape
19+
import androidx.compose.material3.Button
20+
import androidx.compose.material3.MaterialTheme
21+
import androidx.compose.material3.Text
22+
import androidx.compose.runtime.Composable
23+
import androidx.compose.runtime.LaunchedEffect
24+
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.mutableIntStateOf
26+
import androidx.compose.runtime.mutableStateOf
27+
import androidx.compose.runtime.saveable.rememberSaveable
28+
import androidx.compose.runtime.setValue
29+
import androidx.compose.ui.Alignment
30+
import androidx.compose.ui.Modifier
31+
import androidx.compose.ui.layout.onPlaced
32+
import androidx.compose.ui.res.stringResource
33+
import androidx.compose.ui.text.font.FontWeight
34+
import androidx.compose.ui.tooling.preview.Preview
35+
import androidx.compose.ui.unit.IntOffset
36+
import androidx.compose.ui.unit.dp
37+
import androidx.compose.ui.unit.sp
38+
import com.gravatar.quickeditor.R
39+
import com.gravatar.quickeditor.ui.avatarpicker.AvatarUi
40+
import com.gravatar.quickeditor.ui.avatarpicker.AvatarsSectionUiState
41+
import com.gravatar.quickeditor.ui.avatarpicker.fullUrl
42+
import com.gravatar.restapi.models.Avatar
43+
import com.gravatar.ui.GravatarTheme
44+
import java.time.Instant
45+
46+
@Composable
47+
internal fun AvatarsSection(
48+
state: AvatarsSectionUiState,
49+
onAvatarSelected: (Avatar) -> Unit,
50+
onLocalImageSelected: (Uri) -> Unit,
51+
modifier: Modifier = Modifier,
52+
) {
53+
var popupVisible by rememberSaveable { mutableStateOf(false) }
54+
var popupYOffset by rememberSaveable { mutableIntStateOf(0) }
55+
val listState = rememberLazyListState()
56+
57+
val pickMedia = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
58+
uri?.let { onLocalImageSelected(it) }
59+
}
60+
61+
LaunchedEffect(state.scrollToIndex) {
62+
state.scrollToIndex?.let { listState.scrollToItem(it) }
63+
}
64+
65+
Box(
66+
modifier = modifier
67+
.border(
68+
width = 1.dp,
69+
color = MaterialTheme.colorScheme.surfaceContainerHighest,
70+
shape = RoundedCornerShape(8.dp),
71+
)
72+
.padding(16.dp),
73+
) {
74+
Column {
75+
Text(
76+
text = stringResource(id = R.string.avatar_picker_title),
77+
fontSize = 22.sp,
78+
fontWeight = FontWeight.Bold,
79+
)
80+
Text(
81+
text = stringResource(R.string.avatar_picker_description),
82+
fontSize = 15.sp,
83+
color = MaterialTheme.colorScheme.tertiary,
84+
modifier = Modifier.padding(top = 4.dp),
85+
)
86+
87+
LazyRow(
88+
horizontalArrangement = Arrangement.spacedBy(8.dp),
89+
modifier = Modifier.padding(vertical = 24.dp),
90+
state = listState,
91+
) {
92+
items(items = state.avatars, key = { it.avatarId }) { avatarModel ->
93+
when (avatarModel) {
94+
is AvatarUi.Uploaded -> SelectableAvatar(
95+
imageUrl = avatarModel.avatar.fullUrl,
96+
isSelected = avatarModel.isSelected,
97+
isLoading = avatarModel.isLoading,
98+
onAvatarClicked = {
99+
onAvatarSelected(avatarModel.avatar)
100+
},
101+
modifier = Modifier.size(96.dp),
102+
)
103+
104+
is AvatarUi.Local -> LocalAvatar(
105+
imageUri = avatarModel.uri.toString(),
106+
isLoading = true,
107+
modifier = Modifier.size(96.dp),
108+
)
109+
}
110+
}
111+
}
112+
Button(
113+
modifier = Modifier
114+
.fillMaxWidth()
115+
.onPlaced { layoutCoordinates -> popupYOffset = layoutCoordinates.size.height },
116+
onClick = { popupVisible = true },
117+
shape = RoundedCornerShape(4.dp),
118+
contentPadding = PaddingValues(14.dp),
119+
enabled = state.uploadButtonEnabled,
120+
) {
121+
Text(
122+
text = stringResource(R.string.avatar_picker_upload_image),
123+
style = MaterialTheme.typography.titleMedium,
124+
)
125+
}
126+
}
127+
if (popupVisible) {
128+
MediaPickerPopup(
129+
alignment = Alignment.BottomCenter,
130+
onDismissRequest = { popupVisible = false },
131+
offset = IntOffset(0, -popupYOffset - 30),
132+
onChoosePhotoClick = {
133+
popupVisible = false
134+
pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
135+
},
136+
onTakePhotoClick = { popupVisible = false },
137+
)
138+
}
139+
}
140+
}
141+
142+
@Composable
143+
@Preview(showBackground = true)
144+
private fun AvatarSectionPreview() {
145+
GravatarTheme {
146+
AvatarsSection(
147+
state = AvatarsSectionUiState(
148+
avatars = listOf(
149+
AvatarUi.Uploaded(
150+
avatar = Avatar {
151+
imageUrl = "/image/url"
152+
format = 0
153+
imageId = "1"
154+
rating = "G"
155+
altText = "alt"
156+
isCropped = true
157+
updatedDate = Instant.now()
158+
},
159+
isSelected = true,
160+
isLoading = false,
161+
),
162+
),
163+
scrollToIndex = null,
164+
uploadButtonEnabled = true,
165+
),
166+
onAvatarSelected = { },
167+
onLocalImageSelected = { },
168+
)
169+
}
170+
}

0 commit comments

Comments
 (0)