11package team.aliens.dms.android.feature.profile.ui
22
3- import android.Manifest
43import android.net.Uri
5- import android.os.Build
4+ import androidx.activity.compose.rememberLauncherForActivityResult
5+ import androidx.activity.result.PickVisualMediaRequest
6+ import androidx.activity.result.contract.ActivityResultContracts
67import androidx.compose.foundation.background
78import androidx.compose.foundation.border
89import androidx.compose.foundation.clickable
9- import androidx.compose.foundation.layout.Arrangement
1010import androidx.compose.foundation.layout.Box
1111import androidx.compose.foundation.layout.Column
12- import androidx.compose.foundation.layout.PaddingValues
13- import androidx.compose.foundation.layout.aspectRatio
12+ import androidx.compose.foundation.layout.Spacer
1413import androidx.compose.foundation.layout.fillMaxSize
15- import androidx.compose.foundation.layout.padding
14+ import androidx.compose.foundation.layout.height
1615import androidx.compose.foundation.layout.size
1716import androidx.compose.foundation.layout.systemBarsPadding
18- import androidx.compose.foundation.lazy.grid.GridCells
19- import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
20- import androidx.compose.foundation.lazy.grid.items
2117import androidx.compose.foundation.shape.CircleShape
2218import androidx.compose.material.icons.Icons
23- import androidx.compose.material.icons.filled.CheckCircle
24- import androidx.compose.material.icons.outlined.Circle
19+ import androidx.compose.material.icons.filled.AddAPhoto
2520import androidx.compose.material3.Icon
21+ import androidx.compose.material3.Text
2622import androidx.compose.runtime.Composable
2723import androidx.compose.runtime.LaunchedEffect
2824import androidx.compose.runtime.getValue
2925import androidx.compose.runtime.rememberUpdatedState
3026import androidx.compose.ui.Alignment
3127import androidx.compose.ui.Modifier
32- import androidx.compose.ui.graphics.Color
28+ import androidx.compose.ui.draw.clip
3329import androidx.compose.ui.layout.ContentScale
3430import androidx.compose.ui.unit.dp
3531import androidx.core.net.toUri
3632import androidx.hilt.navigation.compose.hiltViewModel
3733import androidx.lifecycle.compose.collectAsStateWithLifecycle
3834import coil.compose.AsyncImage
39- import com.google.accompanist.permissions.ExperimentalPermissionsApi
40- import com.google.accompanist.permissions.isGranted
41- import com.google.accompanist.permissions.rememberPermissionState
42- import kotlinx.collections.immutable.ImmutableList
43- import kotlinx.collections.immutable.toPersistentList
4435import team.aliens.dms.android.core.designsystem.DmsTheme
36+ import team.aliens.dms.android.core.designsystem.labelM
4537import team.aliens.dms.android.core.designsystem.appbar.DmsTopAppBar
4638import team.aliens.dms.android.core.designsystem.button.ButtonColor
4739import team.aliens.dms.android.core.designsystem.button.ButtonType
@@ -50,27 +42,33 @@ import team.aliens.dms.android.core.designsystem.snackbar.DmsSnackBarType
5042import team.aliens.dms.android.feature.profile.viewmodel.SelectProfileSideEffect
5143import team.aliens.dms.android.feature.profile.viewmodel.SelectProfileViewModel
5244
53- @OptIn(ExperimentalPermissionsApi ::class )
5445@Composable
5546internal fun SelectProfile (
5647 onBack : () -> Unit ,
5748 onNavigateAdjustProfile : (String ) -> Unit ,
5849 onShowSnackBar : (DmsSnackBarType , String ) -> Unit ,
5950) {
6051 val viewModel: SelectProfileViewModel = hiltViewModel()
61- val permission = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
62- Manifest .permission.READ_MEDIA_IMAGES
63- } else {
64- Manifest .permission.READ_EXTERNAL_STORAGE
65- }
66- val request = rememberPermissionState(permission = permission)
67-
6852 val state by viewModel.uiState.collectAsStateWithLifecycle()
6953 val updatedOnNavigateAdjustProfile by rememberUpdatedState(onNavigateAdjustProfile)
7054 val updatedOnShowSnackBar by rememberUpdatedState(onShowSnackBar)
7155
56+ val updatedOnBack by rememberUpdatedState(onBack)
57+ val photoPickerLauncher = rememberLauncherForActivityResult(
58+ contract = ActivityResultContracts .PickVisualMedia (),
59+ ) { uri ->
60+ if (uri != null ) {
61+ viewModel.selectImage(uri.toString())
62+ } else if (state.selectedUri.isBlank()) {
63+ updatedOnBack()
64+ }
65+ }
66+
67+ LaunchedEffect (Unit ) {
68+ photoPickerLauncher.launch(PickVisualMediaRequest (ActivityResultContracts .PickVisualMedia .ImageOnly ))
69+ }
70+
7271 LaunchedEffect (Unit ) {
73- request.launchPermissionRequest()
7472 viewModel.sideEffect.collect {
7573 when (it) {
7674 is SelectProfileSideEffect .SuccessProfileImage -> updatedOnNavigateAdjustProfile(it.profileImageUrl)
@@ -81,107 +79,91 @@ internal fun SelectProfile(
8179 }
8280 }
8381
84- LaunchedEffect (request.status.isGranted) {
85- if (request.status.isGranted) {
86- viewModel.loadImagesIfNeeded()
87- }
88- }
89-
9082 SelectProfileScreen (
9183 onBack = onBack,
9284 uploadProfileImage = viewModel::uploadProfileImage,
93- uriList = state.uriList.toPersistentList(),
9485 selectedUri = state.selectedUri,
9586 enabled = state.enabled,
96- onSelectImage = viewModel::selectImage,
87+ onPickImage = {
88+ photoPickerLauncher.launch(PickVisualMediaRequest (ActivityResultContracts .PickVisualMedia .ImageOnly ))
89+ },
9790 )
9891}
9992
10093@Composable
10194private fun SelectProfileScreen (
10295 onBack : () -> Unit ,
10396 uploadProfileImage : (Uri ? ) -> Unit ,
104- uriList : ImmutableList <String >,
10597 selectedUri : String ,
10698 enabled : Boolean ,
107- onSelectImage : (String ) -> Unit ,
99+ onPickImage : () -> Unit ,
108100) {
109101 Column (
110102 modifier = Modifier
111103 .fillMaxSize()
112104 .systemBarsPadding(),
105+ horizontalAlignment = Alignment .CenterHorizontally ,
113106 ) {
114- DmsTopAppBar (onBackClick = onBack, actions = {
115- DmsButton (
116- onClick = { uploadProfileImage(selectedUri.toUri()) },
117- text = " ์ ํ" ,
118- buttonType = ButtonType .Text ,
119- buttonColor = ButtonColor .Primary ,
120- enabled = enabled,
121- )
122- },)
123- LazyVerticalGrid (
124- columns = GridCells .Fixed (3 ),
125- horizontalArrangement = Arrangement .spacedBy(4 .dp),
126- verticalArrangement = Arrangement .spacedBy(4 .dp),
127- contentPadding = PaddingValues (4 .dp),
128- modifier = Modifier .fillMaxSize(),
129- ) {
130- items(uriList) { uri ->
131- val isSelected = selectedUri.isNotEmpty() && selectedUri == uri
132- ImageItem (
133- imageUri = uri,
134- isSelected = isSelected,
135- onClick = { onSelectImage(uri) },
107+ DmsTopAppBar (
108+ onBackClick = onBack,
109+ actions = {
110+ DmsButton (
111+ onClick = { uploadProfileImage(selectedUri.toUri()) },
112+ text = " ์ ํ" ,
113+ buttonType = ButtonType .Text ,
114+ buttonColor = ButtonColor .Primary ,
115+ enabled = enabled,
136116 )
137- }
138- }
117+ },
118+ )
119+ Spacer (modifier = Modifier .weight(1f ))
120+ ProfileImagePicker (
121+ selectedUri = selectedUri,
122+ onPickImage = onPickImage,
123+ )
124+ Spacer (modifier = Modifier .weight(1f ))
139125 }
140126}
141127
142128@Composable
143- fun ImageItem (
144- imageUri : String ,
145- isSelected : Boolean ,
146- onClick : () -> Unit
129+ private fun ProfileImagePicker (
130+ selectedUri : String ,
131+ onPickImage : () -> Unit ,
147132) {
148- Box (
149- modifier = Modifier
150- .aspectRatio(1f )
151- .clickable(onClick = onClick)
133+ val hasImage = selectedUri.isNotEmpty()
134+ Column (
135+ horizontalAlignment = Alignment .CenterHorizontally ,
152136 ) {
153- AsyncImage (
154- model = imageUri,
155- contentDescription = null ,
156- contentScale = ContentScale .Crop ,
157- modifier = Modifier .matchParentSize()
158- )
159- if (isSelected) {
160- Box (
161- modifier = Modifier
162- .matchParentSize()
163- .background(Color .Black .copy(alpha = 0.4f )) // ๋ฐํฌ๋ช
๊ฒ์์
164- )
165- }
166- Icon (
167- imageVector = if (isSelected) Icons .Filled .CheckCircle else Icons .Outlined .Circle ,
168- contentDescription = if (isSelected) " ์ ํ๋จ" else " ์ ํ ์๋จ" ,
169- tint = if (isSelected) DmsTheme .colorScheme.onPrimary else Color .White , // ์ ํ๋๋ฉด ๊ฐ์กฐ์, ์๋๋ฉด ํฐ์
137+ Box (
170138 modifier = Modifier
171- .align(Alignment .TopEnd )
172- .padding(8 .dp)
173- .size(24 .dp)
174- .background(
175- color = if (! isSelected) Color .Black .copy(alpha = 0.2f ) else Color .Transparent ,
176- shape = CircleShape
139+ .size(160 .dp)
140+ .clip(CircleShape )
141+ .background(DmsTheme .colorScheme.surfaceVariant)
142+ .border(2 .dp, DmsTheme .colorScheme.primary, CircleShape )
143+ .clickable(onClick = onPickImage),
144+ contentAlignment = Alignment .Center ,
145+ ) {
146+ if (hasImage) {
147+ AsyncImage (
148+ model = selectedUri,
149+ contentDescription = null ,
150+ contentScale = ContentScale .Crop ,
151+ modifier = Modifier .matchParentSize(),
177152 )
178- )
179- if (isSelected) {
180- Box (
181- modifier = Modifier
182- .matchParentSize()
183- .border(3 .dp, DmsTheme .colorScheme.onPrimary) // ๊ตต์ ํ
๋๋ฆฌ
184- )
153+ } else {
154+ Icon (
155+ imageVector = Icons .Filled .AddAPhoto ,
156+ contentDescription = " ์ฌ์ง ์ ํ" ,
157+ tint = DmsTheme .colorScheme.primary,
158+ modifier = Modifier .size(48 .dp),
159+ )
160+ }
185161 }
162+ Spacer (modifier = Modifier .height(16 .dp))
163+ Text (
164+ text = " ํญํ์ฌ ์ฌ์ง ์ ํ" ,
165+ style = DmsTheme .typography.labelM,
166+ color = DmsTheme .colorScheme.inverseOnSurface,
167+ )
186168 }
187169}
0 commit comments