Skip to content

Commit 793a5ef

Browse files
Create QuickEditor composable that will host the picker and about section
That the component that will also be responsible for the Profile card - displaying the user info, refreshing and showing possible navigation options
1 parent 673db19 commit 793a5ef

File tree

8 files changed

+200
-98
lines changed

8 files changed

+200
-98
lines changed

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

+20-46
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,15 @@ import androidx.lifecycle.Lifecycle
4848
import androidx.lifecycle.compose.LocalLifecycleOwner
4949
import androidx.lifecycle.repeatOnLifecycle
5050
import androidx.lifecycle.viewmodel.compose.viewModel
51-
import com.gravatar.extensions.defaultProfile
5251
import com.gravatar.quickeditor.R
5352
import com.gravatar.quickeditor.ui.components.AlertBanner
5453
import com.gravatar.quickeditor.ui.components.AvatarDeletionConfirmationDialog
5554
import com.gravatar.quickeditor.ui.components.AvatarOption
5655
import com.gravatar.quickeditor.ui.components.AvatarsSection
5756
import com.gravatar.quickeditor.ui.components.CtaSection
5857
import com.gravatar.quickeditor.ui.components.DownloadManagerDisabledAlertDialog
59-
import com.gravatar.quickeditor.ui.components.EmailLabel
6058
import com.gravatar.quickeditor.ui.components.FailedAvatarUploadAlertDialog
6159
import com.gravatar.quickeditor.ui.components.PermissionRationaleDialog
62-
import com.gravatar.quickeditor.ui.components.ProfileCard
63-
import com.gravatar.quickeditor.ui.components.QEPageDefault
6460
import com.gravatar.quickeditor.ui.cropperlauncher.CropperLauncher
6561
import com.gravatar.quickeditor.ui.cropperlauncher.UCropCropperLauncher
6662
import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
@@ -75,7 +71,6 @@ import com.gravatar.quickeditor.ui.withPermission
7571
import com.gravatar.restapi.models.Avatar
7672
import com.gravatar.types.Email
7773
import com.gravatar.ui.GravatarTheme
78-
import com.gravatar.ui.components.ComponentState
7974
import com.yalantis.ucrop.UCrop
8075
import kotlinx.coroutines.CoroutineScope
8176
import kotlinx.coroutines.Dispatchers
@@ -89,8 +84,8 @@ internal fun AvatarPicker(
8984
handleExpiredSession: Boolean,
9085
onAvatarSelected: () -> Unit,
9186
onSessionExpired: () -> Unit,
92-
onDoneClicked: () -> Unit,
9387
onAltTextTapped: (email: String, avatarId: String) -> Unit,
88+
onRefresh: () -> Unit,
9489
viewModel: AvatarPickerViewModel = viewModel(
9590
factory = AvatarPickerViewModelFactory(gravatarQuickEditorParams, handleExpiredSession),
9691
),
@@ -130,23 +125,23 @@ internal fun AvatarPicker(
130125
}
131126
}
132127

133-
GravatarTheme {
134-
QEPageDefault(
135-
onDoneClicked = onDoneClicked,
136-
content = {
137-
Box(modifier = Modifier.wrapContentSize()) {
138-
AvatarPicker(
139-
uiState = uiState,
140-
onEvent = viewModel::onEvent,
141-
)
142-
QESnackbarHost(
143-
modifier = Modifier
144-
.align(Alignment.BottomStart),
145-
hostState = snackState,
146-
)
147-
}
148-
},
149-
)
128+
Surface {
129+
Box(modifier = Modifier.wrapContentSize()) {
130+
AvatarPicker(
131+
uiState = uiState,
132+
onEvent = { event ->
133+
viewModel.onEvent(event)
134+
if (event is AvatarPickerEvent.Refresh) {
135+
onRefresh()
136+
}
137+
},
138+
)
139+
QESnackbarHost(
140+
modifier = Modifier
141+
.align(Alignment.BottomStart),
142+
hostState = snackState,
143+
)
144+
}
150145
}
151146
}
152147

@@ -199,31 +194,19 @@ internal fun AvatarPicker(uiState: AvatarPickerUiState, onEvent: (AvatarPickerEv
199194
),
200195
) {
201196
Column {
202-
EmailLabel(
203-
email = uiState.email,
204-
modifier = Modifier
205-
.fillMaxWidth()
206-
.padding(bottom = 10.dp),
207-
)
208-
ProfileCard(
209-
profile = uiState.profile,
210-
email = uiState.email,
211-
avatarCacheBuster = uiState.avatarCacheBuster.toString(),
212-
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
213-
)
214197
AnimatedVisibility(visible = uiState.nonSelectedAvatarAlertVisible) {
215198
AlertBanner(
216199
message = stringResource(id = R.string.gravatar_qe_alert_banner_no_avatar_selected),
217200
onClose = { onEvent(AvatarPickerEvent.AvatarDeleteAlertDismissed) },
218-
modifier = Modifier.padding(horizontal = 16.dp),
201+
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
219202
)
220203
}
221204
Box(
222205
modifier = Modifier
223206
.fillMaxWidth()
224207
.animateContentSize(),
225208
) {
226-
val sectionModifier = Modifier.padding(top = 16.dp, bottom = 10.dp)
209+
val sectionModifier = Modifier.padding(top = 8.dp, bottom = 10.dp)
227210
when {
228211
uiState.isLoading -> Box(
229212
modifier = sectionModifier
@@ -490,13 +473,6 @@ private fun AvatarPickerPreview() {
490473
AvatarPicker(
491474
uiState = AvatarPickerUiState(
492475
email = Email("[email protected]"),
493-
profile = ComponentState.Loaded(
494-
defaultProfile(
495-
hash = "tetet",
496-
displayName = "Henry Wallace",
497-
location = "London, UK",
498-
),
499-
),
500476
emailAvatars = EmailAvatars(
501477
avatars = listOf(
502478
Avatar {
@@ -523,7 +499,6 @@ private fun AvatarPickerLoadingPreview() {
523499
AvatarPicker(
524500
uiState = AvatarPickerUiState(
525501
email = Email("[email protected]"),
526-
profile = ComponentState.Loading,
527502
isLoading = true,
528503
avatarPickerContentLayout = AvatarPickerContentLayout.Horizontal,
529504
emailAvatars = null,
@@ -540,7 +515,6 @@ private fun AvatarPickerErrorPreview() {
540515
AvatarPicker(
541516
uiState = AvatarPickerUiState(
542517
email = Email("[email protected]"),
543-
profile = null,
544518
isLoading = false,
545519
emailAvatars = null,
546520
error = SectionError.ServerError,

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

-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ package com.gravatar.quickeditor.ui.avatarpicker
33
import android.net.Uri
44
import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
55
import com.gravatar.restapi.models.Avatar
6-
import com.gravatar.restapi.models.Profile
76
import com.gravatar.services.ErrorType
87
import com.gravatar.types.Email
9-
import com.gravatar.ui.components.ComponentState
108

119
internal data class AvatarPickerUiState(
1210
val email: Email,
1311
val avatarPickerContentLayout: AvatarPickerContentLayout,
1412
val isLoading: Boolean = false,
1513
val error: SectionError? = null,
16-
val profile: ComponentState<Profile>? = null,
1714
val emailAvatars: EmailAvatars? = null,
1815
val selectingAvatarId: String? = null,
1916
val uploadingAvatar: Uri? = null,
@@ -22,7 +19,6 @@ internal data class AvatarPickerUiState(
2219
val failedUploadDialog: AvatarUploadFailure? = null,
2320
val downloadManagerDisabled: Boolean = false,
2421
val nonSelectedAvatarAlertVisible: Boolean = false,
25-
val avatarCacheBuster: Long? = null,
2622
) {
2723
val avatarsSectionUiState: AvatarsSectionUiState? = emailAvatars?.mapToUiModel()?.let {
2824
AvatarsSectionUiState(

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

-46
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,12 @@ import com.gravatar.quickeditor.data.FileUtils
1111
import com.gravatar.quickeditor.data.ImageDownloader
1212
import com.gravatar.quickeditor.data.models.QuickEditorError
1313
import com.gravatar.quickeditor.data.repository.AvatarRepository
14-
import com.gravatar.quickeditor.data.repository.ProfileRepository
1514
import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
1615
import com.gravatar.quickeditor.ui.editor.GravatarQuickEditorParams
17-
import com.gravatar.quickeditor.ui.time.Clock
18-
import com.gravatar.quickeditor.ui.time.SystemClock
1916
import com.gravatar.restapi.models.Avatar
2017
import com.gravatar.services.ErrorType
2118
import com.gravatar.services.GravatarResult
2219
import com.gravatar.types.Email
23-
import com.gravatar.ui.components.ComponentState
2420
import kotlinx.coroutines.channels.Channel
2521
import kotlinx.coroutines.flow.MutableStateFlow
2622
import kotlinx.coroutines.flow.StateFlow
@@ -39,18 +35,15 @@ internal class AvatarPickerViewModel(
3935
private val email: Email,
4036
private val handleExpiredSession: Boolean,
4137
private val avatarPickerContentLayout: AvatarPickerContentLayout,
42-
private val profileRepository: ProfileRepository,
4338
private val avatarRepository: AvatarRepository,
4439
private val imageDownloader: ImageDownloader,
4540
private val fileUtils: FileUtils,
46-
private val clock: Clock,
4741
) : ViewModel() {
4842
private val _uiState =
4943
MutableStateFlow(
5044
AvatarPickerUiState(
5145
email = email,
5246
avatarPickerContentLayout = avatarPickerContentLayout,
53-
avatarCacheBuster = clock.getTimeMillis(),
5447
),
5548
)
5649
val uiState: StateFlow<AvatarPickerUiState> = _uiState.asStateFlow()
@@ -186,9 +179,6 @@ internal class AvatarPickerViewModel(
186179

187180
private fun refresh() {
188181
fetchAvatars()
189-
if (uiState.value.profile !is ComponentState.Loaded) {
190-
fetchProfile()
191-
}
192182
}
193183

194184
private fun selectAvatar(avatar: Avatar) {
@@ -203,7 +193,6 @@ internal class AvatarPickerViewModel(
203193
_uiState.update { currentState ->
204194
currentState.copy(
205195
selectingAvatarId = null,
206-
avatarCacheBuster = clock.getTimeMillis(),
207196
)
208197
}
209198
_actions.send(AvatarPickerAction.AvatarSelected)
@@ -248,11 +237,6 @@ internal class AvatarPickerViewModel(
248237
currentState.copy(
249238
uploadingAvatar = null,
250239
scrollToIndex = null,
251-
avatarCacheBuster = if (avatar.selected == true) {
252-
clock.getTimeMillis()
253-
} else {
254-
currentState.avatarCacheBuster
255-
},
256240
)
257241
}
258242
}
@@ -273,25 +257,6 @@ internal class AvatarPickerViewModel(
273257
}
274258
}
275259

276-
private fun fetchProfile() {
277-
viewModelScope.launch {
278-
_uiState.update { currentState -> currentState.copy(profile = ComponentState.Loading) }
279-
when (val result = profileRepository.getProfile(email)) {
280-
is GravatarResult.Success -> {
281-
_uiState.update { currentState ->
282-
currentState.copy(profile = ComponentState.Loaded(result.value))
283-
}
284-
}
285-
286-
is GravatarResult.Failure -> {
287-
_uiState.update { currentState ->
288-
currentState.copy(profile = null)
289-
}
290-
}
291-
}
292-
}
293-
}
294-
295260
private fun fetchAvatars() {
296261
viewModelScope.launch {
297262
fetchAvatars(
@@ -395,15 +360,6 @@ internal class AvatarPickerViewModel(
395360

396361
private suspend fun notifyAvatarDeletedSuccessfully(isSelectedAvatar: Boolean) {
397362
if (isSelectedAvatar) _actions.send(AvatarPickerAction.AvatarSelected)
398-
_uiState.update { currentState ->
399-
currentState.copy(
400-
avatarCacheBuster = if (isSelectedAvatar) {
401-
clock.getTimeMillis()
402-
} else {
403-
currentState.avatarCacheBuster
404-
},
405-
)
406-
}
407363
}
408364

409365
private fun hideNonSelectedAvatarAlert() {
@@ -474,11 +430,9 @@ internal class AvatarPickerViewModelFactory(
474430
handleExpiredSession = handleExpiredSession,
475431
email = gravatarQuickEditorParams.email,
476432
avatarPickerContentLayout = gravatarQuickEditorParams.avatarPickerContentLayout,
477-
profileRepository = QuickEditorContainer.getInstance().profileRepository,
478433
avatarRepository = QuickEditorContainer.getInstance().avatarRepository,
479434
imageDownloader = QuickEditorContainer.getInstance().imageDownloader,
480435
fileUtils = QuickEditorContainer.getInstance().fileUtils,
481-
clock = SystemClock(),
482436
) as T
483437
}
484438
}

gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/editor/GravatarQuickEditorPage.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import androidx.navigation.compose.navigation
1616
import androidx.navigation.compose.rememberNavController
1717
import androidx.navigation.navArgument
1818
import com.gravatar.quickeditor.ui.alttext.AltTextPage
19-
import com.gravatar.quickeditor.ui.avatarpicker.AvatarPicker
2019
import com.gravatar.quickeditor.ui.navigation.EditorNavDestinations
2120
import com.gravatar.quickeditor.ui.navigation.QuickEditorPage
2221
import com.gravatar.quickeditor.ui.oauth.OAuthPage
@@ -156,7 +155,8 @@ private fun NavGraphBuilder.addAvatarPickerGraph(
156155
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Start) + shrinkVertically()
157156
},
158157
) {
159-
AvatarPicker(
158+
it.viewModelStore
159+
QuickEditor(
160160
gravatarQuickEditorParams = gravatarQuickEditorParams,
161161
handleExpiredSession = handleExpiredSession,
162162
onAvatarSelected = onAvatarSelected,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.gravatar.quickeditor.ui.editor
2+
3+
import androidx.compose.foundation.layout.Column
4+
import androidx.compose.foundation.layout.fillMaxWidth
5+
import androidx.compose.foundation.layout.padding
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.collectAsState
8+
import androidx.compose.runtime.getValue
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.unit.dp
11+
import androidx.lifecycle.viewmodel.compose.viewModel
12+
import com.gravatar.quickeditor.ui.avatarpicker.AvatarPicker
13+
import com.gravatar.quickeditor.ui.components.EmailLabel
14+
import com.gravatar.quickeditor.ui.components.ProfileCard
15+
import com.gravatar.quickeditor.ui.components.QEPageDefault
16+
import com.gravatar.ui.GravatarTheme
17+
18+
@Composable
19+
internal fun QuickEditor(
20+
gravatarQuickEditorParams: GravatarQuickEditorParams,
21+
handleExpiredSession: Boolean,
22+
onAvatarSelected: () -> Unit,
23+
onSessionExpired: () -> Unit,
24+
onDoneClicked: () -> Unit,
25+
onAltTextTapped: (email: String, avatarId: String) -> Unit,
26+
viewModel: QuickEditorViewModel = viewModel(
27+
factory = QuickEditorViewModelFactory(gravatarQuickEditorParams),
28+
),
29+
) {
30+
val uiState by viewModel.uiState.collectAsState()
31+
32+
QuickEditor(
33+
uiState = uiState,
34+
onDoneClicked = onDoneClicked,
35+
) {
36+
AvatarPicker(
37+
gravatarQuickEditorParams = gravatarQuickEditorParams,
38+
handleExpiredSession = handleExpiredSession,
39+
onAvatarSelected = onAvatarSelected,
40+
onSessionExpired = onSessionExpired,
41+
onAltTextTapped = onAltTextTapped,
42+
onRefresh = {
43+
viewModel.onEvent(QuickEditorEvent.Refresh)
44+
},
45+
)
46+
}
47+
}
48+
49+
@Composable
50+
internal fun QuickEditor(
51+
uiState: QuickEditorUiState,
52+
onDoneClicked: () -> Unit,
53+
content: @Composable () -> Unit = {},
54+
) {
55+
GravatarTheme {
56+
QEPageDefault(
57+
onDoneClicked = onDoneClicked,
58+
content = {
59+
Column {
60+
EmailLabel(
61+
email = uiState.email,
62+
modifier = Modifier
63+
.fillMaxWidth()
64+
.padding(bottom = 10.dp),
65+
)
66+
ProfileCard(
67+
profile = uiState.profile,
68+
email = uiState.email,
69+
avatarCacheBuster = uiState.avatarCacheBuster.toString(),
70+
modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
71+
)
72+
content()
73+
}
74+
},
75+
)
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.gravatar.quickeditor.ui.editor
2+
3+
internal sealed class QuickEditorEvent {
4+
data object Refresh : QuickEditorEvent()
5+
6+
data object UpdateAvatarCache : QuickEditorEvent()
7+
}

0 commit comments

Comments
 (0)